I have a users table like this
create table(:users) do
add(:email, :citext)
add(:email_confirmation_token, :string)
add(:email_confirmed_at, :utc_datetime)
add(:deleted_at, :utc_datetime)
timestamps()
end
and I have User schema as
schema "users" do
field(:email, :string)
field(:deleted_at, :utc_datetime)
field(:email_confirmed_at, :utc_datetime)
field(:email_confirmation_token, :string, default: ExApi.Utils.Random.string())
timestamps()
end
Now if I insert record with on_conflict option I want to reset deleted_at but deleted_at is never passed to changeset. I used following line of code
attrs = %{"email" => "tanweer.shahzaad#gmail.com", "deleted_at" => nil}
cs = %User{} |> User.changeset(attrs)
ExApi.Repo.insert(cs, on_conflict: :replace_all, conflict_target: :email)
Any workaround which will reset deleted_at will be very much appreciated.
Problem: if deleted_at is already set, I want to reset it back to nil on insert
Related
I'm using Rails 5 and PostGres 9.5. I have created this migration
class CreateSearchCodeTable < ActiveRecord::Migration[5.0]
def change
create_table :search_codes do |t|
t.string :code
t.references :address, index: true, foreign_key: true, on_delete: :cascade
t.index ["code"], name: "index_search_codes_on_code", unique: true, using: :btree
end
end
end
The ID column of the address table is not an integer though. Maybe for that reason, I get the below error when I run the migration
== 20171011202623 CreateSearchCodeTable: migrating ============================
-- create_table(:search_codes)
rake aborted!
StandardError: An error has occurred, this and all later migrations canceled:
PG::DatatypeMismatch: ERROR: foreign key constraint "fk_rails_6bd9792e3b" cannot be implemented
DETAIL: Key columns "address_id" and "id" are of incompatible types: integer and character varying.
: CREATE TABLE "search_codes" ("id" serial primary key, "code" character varying, "address_id" integer, CONSTRAINT "fk_rails_6bd9792e3b"
FOREIGN KEY ("address_id")
REFERENCES "addresses" ("id")
)
How do I instruct my migration to create my column with the same type as the referenced column?
t.references delegates job to add_reference and this one accepts :type parameter. Judging from this, you should be able to do
t.references :address, type: :string, index: true, foreign_key: true, on_delete: :cascade
^^^^^^^^^^^^^
Just tested this on a toy sqlite3-based project, it worked. Should "just work" on pg too.
How do I instruct my migration to create my column with the same type as the referenced column?
If you meant to tell it to infer the type of the other column and have type of this one match it, then this is likely not possible. But you can always specify type explicitly.
My code is resulting in a rare double or triple insert into the database and I am at a loss as to why. It is very difficult to reproduce but I can look at the timestamps to see the created at time is basically the same when it happens. I believe it only occurs when the CardMeta is not already found.
I figure I need to add a unique key or wrap it in a transaction.
def get_or_create_meta(user, card) do
case Repo.all(from c in CardMeta, where: c.user_id == ^user.id,
where: c.card_id == ^card.id) do
[] ->
%CardMeta{}
metas ->
hd metas
end
end
def bury(user, card) do
get_or_create_meta(user, card)
|> Repo.preload([:card, :user])
|> CardMeta.changeset(%{last_seen: DateTime.utc_now(), user_id: user.id, card_id: card.id,
learning: false, known: false, prev_interval: 0})
|> Repo.insert_or_update
end
Edit: adding changeset source
def changeset(struct, params \\ %{}) do
struct
|> cast(params, [:last_seen, :difficulty, :prev_interval, :due, :known, :learning,
:user_id, :card_id])
|> assoc_constraint(:user)
|> assoc_constraint(:card)
end
Calling bury from the controller
def update(conn, %{"currentCardId" => card_id, "command" => command}) do
# perform some update on card
card = Repo.get!(Card,card_id)
user = Guardian.Plug.current_resource(conn)
case command do
"fail" ->
SpacedRepetition.fail(user, card)
"learn" ->
SpacedRepetition.learn(user, card)
_ ->
SpacedRepetition.bury(user, card)
end
sendNextCard(conn, user)
end
Edit:
I noticed the last_seen field is microseconds different between duplicated rows, whereas the create_at field does not have that resolution. Thus I suspect the insert_or_update call is fine, but the controller is firing twice before the DB updates. This could be something on the client side, which I don't want to think about. So I am just going to add a unique key.
As an alternative to #aliCna's answer, if you don't want to change the primary key on CardMeta, you can put a unique index constraint in the database with a migration:
defmodule YourApp.Repo.Migrations.AddCardMetaUniqueIndex do
use Ecto.Migration
def change do
create unique_index(
:card_meta,
[:card_id, :user_id],
name: :card_meta_unique_index)
end
end
Which you can then handle in your changeset to produce nice errors if conflicts occur:
def changeset(struct, params \\ %{}) do
struct
|> cast(params, [:last_seen, :difficulty, :prev_interval, :due, :known, :learning,
:user_id, :card_id])
|> assoc_constraint(:user)
|> assoc_constraint(:card)
|> unique_constraint(:user_id, name: :card_meta_unique_index)
end
I believe you can solve this by adding a composite primary key on user_id and card_id
defmodule Anything.CardMeta do
use Anything.Web, :model
#primary_key false
schema "card_meta" do
field :user_id, :integer, primary_key: true
field :card_id, :integer, primary_key: true
. . .
timestamps()
end
end
If this does't solve your problem please add your data model here!
I'm not clear on how to index embedded structs stored as JSONB with Ecto2/Postgres 9.4+
I have a schema with two embedded structs using embeds_one and embeds_many. They are ecto :map fields represented in Postgres as JSONB. I am wondering how I can be sure they are indexed (using Gin?) for speedy queries? I am not sure if this happens automagically, if I need to add an index to my migration or if I need to do it manually using psql etc..
Just looking for clarification on how this works.
Thanks!
defmodule App.Repo.Migrations.CreateClient
def change do
create table(:clients) do
add :name, :string
add :settings, :map
add :roles, {:array, :map}, default: []
timestamps()
end
// This works for normal schema/model fields
create index(:clients, [:name], unique: true, using: :gin)
// BUT CAN I INDEX MY EMBEDS HERE?
// GUESS:
create index(:clients, [:settings], using: :gin)
end
end
defmodule App.Client do
schema "client" do
field :name, :string
embeds_one :settings, Settings // single fixed schema "Settings" model
embeds_many :roles, Role // array of "Role" models
end
end
defmodule Settings do
use Ecto.Model
embedded_schema do // ALSO
field :name, :string // are types relevant?
field :x_count, :integer // stored as strings (INDEXED?)
field :is_active, :boolean // deserialized via cast?
end
end
defmodule Role do
use Ecto.Model
embedded_schema do
field :token
field :display_english
field :display_spanish
end
end
I think you just need to add this:
create index(:clients, [:name], unique: true, using: :gin)
to your migration file.
Or if the index sql statement is gonna be complicated, you could do it with execute so it would be something like this:
execute("CREATE INDEX clients_name_index ON clients USING GIN (name)")
I have not tested it but I believe it should work.
I have a document with an embedded list of sub-docs. How do I update/change one particular document in the embedded list with Ecto?
defmodule MyApp.Thing do
use MyApp.Model
schema "things" do
embeds_many :users, User
end
end
defmodule MyApp.User do
use MyApp.Model
embedded_schema do
field :name, :string
field :email, :string
field :admin, :boolean, default: false
end
end
defmodule MyApp.Model do
defmacro __using__(_) do
quote do
use MyApp.Web, :model
#primary_key {:id, :binary_id, autogenerate: true}
#foreign_key_type :binary_id # For associations
end
end
end
My solution so far is to generate a list of all users except the one I want to update and make a new list of the one user's changeset and the other users and then put_embed this list on the thing. It works but it feels like there must be a more elegant solution to this.
user = Enum.find(thing.users, fn user -> user.id == user_id end)
other_users = Enum.filter(thing.users, fn user -> user.id != user_id end)
user_cs = User.changeset(user, %{email: email})
users = [user_cs | other_users]
thing
|> Ecto.Changeset.change
|> Ecto.Changeset.put_embed(:users, users)
|> Repo.update
EDIT: I just discovered a serious pitfall with this "solution". The untouched users get updated as well which can be a problem with concurring calls (race condition). So there has to be another solution.
I want to modify Devise to make it work with a users table with a UUID primary key with PostgreSQL.
Here is the migration:
class DeviseCreateUsers < ActiveRecord::Migration
def change
create_table :users, id: false do |t|
t.uuid :uuid, null: false
# ...
end
change_table :users do |t|
t.index :uuid, unique: true
# ...
end
end
def migrate(direction)
super
if direction == :up
# This is only necessary because the following does not work:
# t.uuid :uuid, primary: true, null: false
execute "ALTER TABLE users ADD PRIMARY KEY (uuid);"
end
end
end
Here is the User model:
class User < ActiveRecord::Base
primary_key = :uuid
devise :database_authenticatable, :recoverable, :registerable,
:rememberable, :trackable, :validatable
validates :uuid, presence: true
before_validation :ensure_uuid
def ensure_uuid; self.uuid ||= SecureRandom.uuid end
end
Here is the error:
PG::Error: ERROR: operator does not exist: uuid = integer
LINE 1: ...ECT "users".* FROM "users" WHERE "users"."uuid" = 1 ORDER...
^
HINT: No operator matches the given name and argument type(s). You might need to add explicit type casts.
: SELECT "users".* FROM "users" WHERE "users"."uuid" = 1 ORDER BY "users"."uuid" ASC LIMIT 1
Extracted source (around line #5):
1 .navbar-inner
2 .container
3 = a_nav_tag "App", root_path
4 - if user_signed_in?
5 %ul.nav.pull-right
6 %li.dropdown#user_menu
7 %a.dropdown-toggle(data-toggle="dropdown" href="#")
As you can see above, user_signed_in? is broken. I expect there are several changes needed to move from a 'normal' auto-incrementing ID to a UUID.
For now, I'm just posting the question. I'll take a swing at this later today. If you happen to know how to do this -- or know of a Devise fork, I'd appreciate it.
I've done this in Rails 4 simply by making the id column a uuid data type when creating the table, and no other configuration changes whatsoever. ie. do not create a column named 'uuid', just change the type of the 'id' column to uuid.
Just clear your browser's cookie for the web app (in my case, localhost). The error above is caused because the session was retaining the old user primary key, 1.
After that, things work in my testing. I hope this isn't just luck, it would be a good design if Devise was agnostic about the primary key. (In Devise's code, I saw no use of .id except in some tests.)
2020 answer:
when creating the users table, set the ID as uuid
def change
enable_extension 'pgcrypto' # needed if not already enabled
create_table :users, id: :uuid do |t|
t.string :email,
...