Error
Here's the error that I encountered when I'm testing my Account changeset. It seems like it would only be caused by the Ecto migration with wrongly structured database, but the ecto.migrate runs fine, as also Postgresql doesn't throw any error when I'm trying to insert a row using a similar changeset below.
** (Postgrex.Error) ERROR 23502 (not_null_violation): null value in column "email" violates not-null constraint
table: accounts
column: email
Failing row contains (118, 66168645856, 1, 2018-08-17 03:19:12.176247, 2018-08-17 03:19:12.17626, null, null, null, null).
code: account = insert(:account)
stacktrace:
(ecto) lib/ecto/adapters/sql.ex:554: Ecto.Adapters.SQL.struct/8
(ecto) lib/ecto/repo/schema.ex:547: Ecto.Repo.Schema.apply/4
(ecto) lib/ecto/repo/schema.ex:213: anonymous fn/14 in Ecto.Repo.Schema.do_insert/4
(ecto) lib/ecto/repo/schema.ex:125: Ecto.Repo.Schema.insert!/4
test/schema/account_test.exs:26: (test)
Ecto migrations
migration_create_account.ex
def change do
create table(:accounts) do
add :phone_number, :string
add :access_level, :integer
timestamps()
end
end
migration_add_account.ex
def change do
alter table(:accounts) do
add :email, :string
add :auth_token, :string
add :auth_token_expires_at, :utc_datetime
add :signed_in_at, :utc_datetime
end
create unique_index(:accounts, :email, where: "email IS NOT NULL")
create unique_index(:accounts, [:phone_number], where: "phone_number IS NOT NULL")
end
ExMachina
factory.ex
def account_factory do
random_mobile_number = Enum.map(0..10, fn _i -> :rand.uniform(9) end)
|> List.foldl("", fn i, acc -> acc <> "#{i}" end)
%Account{
phone_number: random_mobile_number,
access_level: 1
}
end
ExUnit
account_test.exs
describe "Account.changeset/2" do
test "should check for valid phone number" do
account = insert(:account)
negative_number = %{phone_number: "-123233239" }
refute changeset(account, negative_number).valid?
end
end
Ecto schema and changeset
schema "accounts" do
field :email , :string
field :phone_number, :string
field :access_level , :integer
field :access_level_text, :string, virtual: true
field :auth_token , :string
field :auth_token_expires_at, :naive_datetime
field :signed_in_at , :naive_datetime
timestamps()
end
#required_params ~w(phone_number email access_level access_level_text)
def changeset(account, attrs) do
account
|> cast(attrs, #required_params)
|> cast_access_level_text()
|> validate_required([:access_level])
|> validate_required_contact_handle()
|> validate_number(:access_level, less_than: 3, greater_than: 0)
|> validate_subset(:access_level_text, #access_levels)
|> validate_format(:email, #email_regex)
|> validate_format(:phone_number, #phone_number_regex)
|> unique_constraint(:phone_number)
end
Thanks guys. What happened in my case is that because I changed migrations after using ecto.migrate, so that the migration changes differs between the test database and development database.
I just ran MIX_ENV=test mix ecto.reset to sync database between the environments.
Related
This is the code.
def create_user(self, id: str, password: str):
query=SQL(
"CREATE USER {id} WITH PASSWORD {password}".format(
id=id
password=password
)
)
try:
self.cur.execute(
query=query
)
except DuplicateObject:
print("{id} User already created.".format(id=id))
else:
print("{id} User create.".format(id=id))
in the code below
query=SQL(
"CREATE USER {id} WITH PASSWORD {password}".format(
id=id
password=password
)
)
I am trying to use the class of the sql module for the id and password variables of the query.
id=Identifier(id)
password=SQL(password)
like the code above.
Using identifiers and SQL will result in a syntax error.
Which class fits the id password variable?
I have an existing database and there is a unique index in that table
ALTER TABLE ONLY users ADD CONSTRAINT unique_document_id UNIQUE (document_id);
I dont have any migration in Ecto, and I want to insert a new record using a changeset. Here is the code from the model and the code to insert
defmodule User do
use Ecto.Schema
schema "users" do
field :name
field :email
field :document_id
end
def signup(name, id_number, email) do
changeset = User.changeset(%User{}, %{name: name,
email: email,
document_id: id_number})
if changeset.valid? do
IO.inspect "the chagenset is valid"
user = case Repo.insert(changeset) do
{:ok, model} -> {:ok, model }
{:error, changeset} -> {:error, changeset.errors}
end
end
def changeset(driver, params \\ :empty) do
driver
|> cast(params, [:document_id, :email, :name])
|> validate_required([:document_id, :email, :name])
|> unique_constraint(:document_id)
|> unique_constraint(:email)
end
end
end
But when I try to insert a duplicated user I get this error and changeset.valid? is true
11:04:17.896 [error] #PID<0.434.0> running App terminated
Server: localhost:4000 (http)
Request: POST /api/signup
** (exit) an exception was raised:
** (Ecto.ConstraintError) constraint error when attempting to insert struct:
* unique: unique_document_id
If you would like to convert this constraint into an error, please
call unique_constraint/3 in your changeset and define the proper
constraint name. The changeset defined the following constraints:
* unique: users_document_id_index
(ecto) lib/ecto/repo/schema.ex:493: anonymous fn/4 in Ecto.Repo.Schema.constraints_to_errors/3
(elixir) lib/enum.ex:1229: Enum."-map/2-lists^map/1-0-"/2
(ecto) lib/ecto/repo/schema.ex:479: Ecto.Repo.Schema.constraints_to_errors/3
(ecto) lib/ecto/repo/schema.ex:213: anonymous fn/13 in Ecto.Repo.Schema.do_insert/4
(ecto) lib/ecto/repo/schema.ex:684: anonymous fn/3 in Ecto.Repo.Schema.wrap_in_transaction/6
(ecto) lib/ecto/adapters/sql.ex:620: anonymous fn/3 in Ecto.Adapters.SQL.do_transaction/3
(db_connection) lib/db_connection.ex:1275: DBConnection.transaction_run/4
(db_connection) lib/db_connection.ex:1199: DBConnection.run_begin/3
You need to specify the constraint name in the call to unique_constraint since it's not the default Ecto convention (which would be users_document_id_index, as the error message says):
|> unique_constraint(:document_id, name: :unique_document_id)
If you have a unique constraint name for the email as well which is not users_name_index, you'll need to do the same for unique_constraint(:name) as well.
I have a web app in which I have the following relationship between two tables, a Topics and Categories table in which a Category has many Topics and a Topic belongs to a Category.
class Topic < ActiveRecord::Base
has_many :comments, dependent: :destroy
belongs_to :category
belongs_to :user
validates :subject, :body, :user_id, :category_id, presence: true
private
def self.find_by_id(params)
if params[:topic_id]
find(params[:topic_id])
else
find(params[:id])
end
end
def self.build_topic_comment(params, comment_params)
#topic = Topic.find_by_id(params)
#topic.comments.build(comment_params)
end
def self.load_comments(topic)
topic.comments.build
end
end
class Category < ActiveRecord::Base
has_many :topics, dependent: :destroy
belongs_to :user
validates :name, :user_id, presence: true
private
def self.find_by_id(params)
if params[:category_id]
find(params[:category_id])
else
find(params[:id])
end
end
def self.load_topics_desc(category)
category.topics.order(created_at: :desc)
end
def self.build_category_topic(params, topic_params)
#category = Category.find_by_id(params)
#category.topics.build(topic_params)
end
end
I added the functionality to move a topic to a different category by updating a topic's foreign key called category id. This is done trough an admin panel I coded myself and it is working correctly. When I checked the topics index page he category column is indeed updated.
The issue is that the rspec test I created is failing and giving me the following error:
Admin::TopicsController with administrator access PATCH #update with valid attributes updates a topic
Failure/Error: if #topic.update(topic_params)
ActiveRecord::InvalidForeignKey:
PG::ForeignKeyViolation: ERROR: insert or update on table "topics" violates foreign key constraint "fk_rails_d5d593e6f0"
DETAIL: Key (category_id)=(3) is not present in table "categories".
: UPDATE "topics" SET "subject" = $1, "body" = $2, "category_id" = $3, "updated_at" = $4 WHERE "topics"."id" = $5
# ./app/controllers/admin/topics_controller.rb:19:in `update'
# /Users/Beno/.rvm/gems/ruby-2.2.2/gems/devise-4.1.1/lib/devise/test_helpers.rb:19:in `block in process'
# /Users/Beno/.rvm/gems/ruby-2.2.2/gems/devise-4.1.1/lib/devise/test_helpers.rb:75:in `catch'
# /Users/Beno/.rvm/gems/ruby-2.2.2/gems/devise-4.1.1/lib/devise/test_helpers.rb:75:in `_catch_warden'
# /Users/Beno/.rvm/gems/ruby-2.2.2/gems/devise-4.1.1/lib/devise/test_helpers.rb:19:in `process'
# ./spec/controllers/admin/topics_controller_spec.rb:79:in `block (5 levels) in <top (required)>'
# ------------------
# --- Caused by: ---
# PG::ForeignKeyViolation:
# ERROR: insert or update on table "topics" violates foreign key constraint "fk_rails_d5d593e6f0"
# DETAIL: Key (category_id)=(3) is not present in table "categories".
# ./app/controllers/admin/topics_controller.rb:19:in `update'
The rspec test file:
require 'rails_helper'
RSpec.describe Admin::TopicsController, type: :controller do
describe 'with administrator access' do
let(:valid_attributes) { attributes_for(:topic) }
let(:invalid_attributes) { attributes_for(:topic, category_id: nil) }
let(:updated_attributes) { attributes_for(:topic, category_id: 3) }
before(:each) do
#topic = create(:topic)
admin = create(:admin)
sign_in admin
end
describe 'GET #index' do
it 'renders the index template' do
get :index
expect(response).to render_template(:index)
end
it 'loads all the topics in the database' do
get :index
expect(assigns(:topics)).to eq([#topic])
end
end
describe 'GET #show' do
it 'renders the show template' do
get :show, id: #topic
expect(response).to render_template(:show)
end
it 'retrieves a topic from the database' do
get :show, id: #topic
expect(assigns(:topic)).to eq(#topic)
end
it 'loads the topics comments' do
topic = create(:topic_with_comments)
get :show, id: topic
expect(topic.comments.length).to eq(5)
end
end
describe 'GET #edit' do
it 'renders the edit template' do
get :edit, id: #topic
expect(response).to render_template(:edit)
end
it 'retrieves a topic from the database' do
get :edit, id: #topic
expect(assigns(:topic)).to eq(#topic)
end
end
describe 'PATCH #update' do
context 'with valid attributes' do
it 'finds a topic in the database' do
get :edit, id: #topic
expect(assigns(:topic)).to eq(#topic)
end
it 'updates a topic' do
patch :update, id: #topic, topic: updated_attributes
#topic.reload
expect(assigns(:topic)).to eq(3)
end
it 'redirects to admin topic path' do
patch :update, id: #topic, topic: updated_attributes
expect(response).to redirect_to(admin_topics_path)
end
end
context 'with invalid attributes' do
it 'does not update a topic'
it 're-renders the edit template'
end
end
describe 'GET #new' do
it 'renders the new template'
it 'creates a new topic'
end
describe 'PUT #create' do
context 'with valid attributes' do
it 'saves a topic in the database'
it 'redirects to created topic'
end
context 'without valid attributes' do
it 'does not save a topic in the database'
it 're-renders the new template'
end
end
describe 'DESTROY #delete' do
it 'finds a topic in the database'
it 'deletes a topic from the database'
it 'redirects to admin topics index'
end
end
end
The admin topics controller:
class Admin::TopicsController < Admin::BaseController
def index
#topics = Topic.all
end
def show
#topic = Topic.find_by_id(params)
end
def edit
#topic = Topic.find_by_id(params)
#topic_options = Topic.all.collect { |topic| [ topic.category.name, topic.category_id ] }.uniq
end
def update
#topic = Topic.find_by_id(params)
if #topic.update(topic_params)
redirect_to admin_topic_path(#topic), notice: 'Topic updated successfully'
else
render :edit
end
end
private
def topic_params
params.require(:topic).permit(:subject, :body, :category_id)
end
end
Can someone point me in the right direction in regards to why the tests could be failing and the app is working correctly?
Thanks!
I'm working on some Phoenix framework and i have encountered a weird problem (as usual). Whenever I try to create some Users, i get User with all fields set to nil. I'm using Mongo.Ecto/
def post_login(conn, %{"login" => login, "password" => password}) do
# IO.inspect Plug.Conn.read_body(conn)
a = User.changeset(%User{}, %{"login" => "login", "password" => "password"})
IO.inspect a
Repo.insert( a )
redirect conn, to: "/default"
end
And the model:
defmodule HelloWorld.User do
use HelloWorld.Web, :model
#primary_key {:id, :binary_id, autogenerate: true}
schema "users" do
field :login, :string
field :password, :string
end
#required_fields ~w()
#optional_fields ~w()
def changeset(model, params \\ :empty) do
model
|> cast(params, #required_fields, #optional_fields)
end
end
And the screen from console:
As you can see in the picture, both login and password fields are nils which makes me feel I've done something incredibly stupid.
The fields need to exist in the options to the cast/4 function:
#required_fields ~w()
#optional_fields ~w(login password)
def changeset(model, params \\ :empty) do
model
|> cast(params, #required_fields, #optional_fields)
end
Anything that is in required_fields but not in the params will add an error to that field on the changeset. If you want the fields to be required just move them to the required_fields list.
Hi I followed the Sunil tutorial but I'm getting the following db error:
Completed 500 Internal Server Error in 11.2ms
pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod
FROM pg_attribute a LEFT JOIN pg_attrdef d
Processing by Refinery::PagesController#home as HTML
Parameters: {"locale"=>:es}
SELECT a.attname, format_type(a.atttypid, a.atttypmod),
ActionView::Template::Error (PG::UndefinedTable: ERROR:
relation "refinery_project_translations" does not exist
2014-12-29T14:14:46.684169+00:00 app[web.1]:
LINE 5: WHERE a.attrelid = '"refinery_project_translati...
2014-12-29T14:14:46.684177+00:00 app[web.1]:
ON a.attrelid = d.adrelid AND a.attnum = d.adnum
Migration file:
class CreateProjectTranslations < ActiveRecord::Migration
def up
::Refinery::Projects::Project.create_translation_table!({:description => :string},
:migrate_data => true)
remove_column :refinery_projects, :description
end
def self.down
add_column :refinery_projects, :description, :string
::Refinery::Projects::Project.drop_translation_table! :migrate_data => true
end
end
after running the migration the I realize is not not creating the the translation table for projects.
Model:
module Refinery
module Projects
class Project < Refinery::Core::BaseModel
self.table_name = 'refinery_projects'
translates :description
attr_accessible :name, :description, :date_started, :date_conclusion,
:position, :relevance, :img_id, :project_id, :status
validates :name, :presence => true, :uniqueness => true
has_many :project_images
has_many_page_images
class Translation
attr_accessible :locale
end
end
end
end
what I'm I missing here?
I solved this issue moving the migration file from the extension to the app migration folder. After that the application was able to create the table for the translation.
Below is a command to generate the extension. If to use it on an existing one, it`ll add all required code to already existing files by overriding them. Be careful with that - commit files before.
rails g refinery:engine Service title:string description:text icon:image --i18n title description