I have a User model which has many roles. Roles contains a user_id field, which I want to validate_presence_of
The issue is: if I assign a role to user upon create, the validation fails because no user_id is set. Now, I do want to validate that a user_id exists, but I need to save the user before checking that.
The code currently looks like this:
#user = User.new(params[:user])
#user.roles << Role.new(:name => 'Peon') unless #user.has_roles?
if #user.save
# ...
The only ways I can think of getting around the problem involves either disabling the validation, which I don't want to do, or double-saving to the DB, which isn't exactly efficient.
What's the standard way for handling this issue?
After researching a bit, this solution seems to be easiest. First, in your Role model, instead of validating user_id, validate user:
validates :user, :presence => true
Then, in your User model, add :inverse_of => :user to your has_many call:
has_many :roles, :inverse_of => :user
Then it works as expected:
irb(main):001:0> #user = User.new
=> #<User id: nil, created_at: nil, updated_at: nil>
irb(main):002:0> #user.roles << Role.new(:name => "blah")
=> [#<Role id: nil, user_id: nil, name: "blah", created_at: nil, updated_at: nil>]
irb(main):003:0> #user.roles[0].user
=> #<User id: nil, created_at: nil, updated_at: nil>
irb(main):004:0> #user.save
(0.1ms) begin transaction
SQL (3.3ms) INSERT INTO "users" ("created_at", "updated_at") VALUES (?, ?) [["created_at", Fri, 04 Jan 2013 02:29:33 UTC +00:00], ["updated_at", Fri, 04 Jan 2013 02:29:33 UTC +00:00]]
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = 3 LIMIT 1
SQL (0.2ms) INSERT INTO "roles" ("created_at", "name", "updated_at", "user_id") VALUES (?, ?, ?, ?) [["created_at", Fri, 04 Jan 2013 02:29:34 UTC +00:00], ["name", "blah"], ["updated_at", Fri, 04 Jan 2013 02:29:34 UTC +00:00], ["user_id", 3]]
(1.9ms) commit transaction
=> true
irb(main):005:0> #user.roles.first
=> #<Role id: 4, user_id: 3, name: "blah", created_at: "2013-01-04 02:29:34", updated_at: "2013-01-04 02:29:34">
Note, however, that this still produces two SQL transactions, one to save the user and one to save the role. I don't see how you can avoid that.
See also: How can you validate the presence of a belongs to association with Rails?
I think you can get around the validation problem if you change your code to look like this:
#user = User.new(params[:user])
#user.roles.new(:name => 'Peon') unless #user.has_roles?
if #user.save
# ...
If that doesn't work, you could try changing you validation to this:
class Role < ActiveRecord::Base
belongs_to :user
validates :user_id, :presence => true, :unless => Proc.new() {|r| r.user}
end
You must take a look at ActiveRecord's Callbacks. Probably you will use the before_validation to do it.
For anyone Googling for a solution to this problem for a has_many :through association, as of 5/December/2013 the :inverse_of option can't be used in conjunction with :through (source). Instead, you can use the approach suggested by #waldyr.ar. For example, if our models are set up as follows:
class User < ActiveRecord::Base
has_many :roles
has_many :tasks, through: roles
end
class Role < ActiveRecord::Base
belongs_to :user
belongs_to :task
end
class Task < ActiveRecord::Base
has_many :roles
has_many :users, through: roles
end
We can modify our Role class as follows to validate the presence of both task and user before saving
class Role < ActiveRecord::Base
belongs_to :user
belongs_to :task
before_save { validates_presence_of :user, :task }
end
Now if we create a new User and add a couple tasks like so:
>> u = User.new
>> 2.times { u.tasks << Task.new }
Running u.save will save the User and the Task, as well as transparently build and save a new Role whose foreign keys user_id and task_id are set appropriately. Validations will run for all models, and we can go on our merry way!
Related
I'm currently trying to implement a Category model to my application. I'm trying to design it in a way that Users can have many Categories, and so can Groups.
The problem I'm running into is that I also want to be able to just have a normal list of Categories without them being assigned to any User or Group.
I was referencing rubyonrails.org/association_basics.
class CreateCategories < ActiveRecord::Migration[5.0]
def change
create_table :categories do |t|
t.string :name
t.text :description
t.references :categorizable, polymorphic: true, index: true
t.timestamps
end
end
end
class Category < ApplicationRecord
belongs_to :categorizable, :polymorphic => true
end
class User < ApplicationRecord
has_many :categories, :as => :categorizable
end
class Group< ApplicationRecord
has_many :categories, :as => :categorizable
end
I'm trying to create a new Category through rails c, but whenever I try to save, it rolls back my transaction probably because I'm missing some condition.
Category(id: integer, name: string, description: text, created_at: datetime, updated_at: datetime)
Category.create( :id => 1, :name => 'Category_1', :description => '' )
begin transaction
rollback transaction
I also feel like there is a better way to create a new Category, as I shouldn't be setting the id manually.
Thanks for your help.
In rails 5, whenever you define a belongs_to association, it is required to have the associated record present by default. You would see this when you look at the errors after trying to create the category object
category = Category.create(:name => 'Category_1', :description => '' )
category.errors.full_messages.to_sentence
If you want to be able to save a record without the belongs_to association, you would have to specify it explicitly
class Category < ApplicationRecord
belongs_to :categorizable, polymorphic: true, required: false
end
If you try to create a new category and see the error is that there needs to exists a categorizable record in order for the category to be created, an easy way to do it is to put the new object itself as the categorizable one and it should do the trick.
$ category = Category.new
=> #<Category id: nil, name: nil, description: nil, categorizable_type: nil, categorizable_id: nil, created_at: nil, updated_at: nil>
$ category.save
(0.1ms) begin transaction
(0.1ms) rollback transaction
=> false
$ category.errors.full_messages
=> ["Categorizable must exist"]
$ category = Category.new(categorizable: category)
=> #<Category id: nil, name: nil, description: nil, categorizable_type: "Category", categorizable_id: nil, created_at: nil, updated_at: nil>
$ category.save
(0.1ms) begin transaction
SQL (1.3ms) INSERT INTO "categories" ("categorizable_type", "created_at", "updated_at") VALUES (?, ?, ?) [["categorizable_type", "Category"], ["created_at", 2017-01-15 00:08:55 UTC], ["updated_at", 2017-01-15 00:08:55 UTC]]
(0.7ms) commit transaction
=> true
This should help, Rails Cast on Polymorphic
https://www.youtube.com/watch?v=6l9EAuev16k
You can create polymorphic records with this...
`#category = #categorizable.User.new`
`#category = #categorizable.Group.new`
So you do not need the id.
I'm trying to model a system where each user may have many emails (at least one).
Following good normalization rules I created two migrations (some fields removed for brevity):
create_table :users do |t|
end
create_table :user_emails do |t|
t.integer :user_id, null: false
t.string :email, null: false
end
add_index :user_emails, :email, :unique => true
add_foreign_key :user_emails, :users, dependent: :delete
and the following rails models:
class User < ActiveRecord::Base
has_many :emails, class_name: 'UserEmail', dependent: :destroy
def self.find_by_email(email)
UserEmail.find_by(email: email).try(:user)
end
validate do
if emails.count < 1
errors.add(:emails, "is empty")
end
end
end
class UserEmail < ActiveRecord::Base
belongs_to :user
validates_presence_of :user_id, :email
validates_uniqueness_of :email
end
Now I am not able to create any of those. User cannot be created since it requires at least an UserEmail. At the same time, UserEmail cannot be created beforehand since it requires an user_id.
I believe I have tried any combination of #user.emails.build and #user.emails << e that I can think of.
How can I solve this really simple problem without renouncing to data consistency (one is saved and the other is not)?
P.s.: I tought that maybe relaxing the validations and using transactions may solve the problem consistently. However I've never used transactions in rails, so any help is really appreciated.
Thanks
validates_presence_of :user_id only check for any valid integer which might not be a valid user.
But if you use validates_presence_of :user and validates_presence_of :emails, they will check the user and emails association are valid and not blank.
Also, when you are trying to create the user and the associated email, you are able to do the following code on application level.
user = User.new(name: 'user name')
user.emails.build(email_address: 'email address') #build(email: '') for your case.
Then, you can save to database with
user.save!
Below is the code I tested with.
class User < ActiveRecord::Base
has_many :emails, class_name: 'UserEmail', dependent: :destroy
validates_presence_of :emails, :message => 'User should have at least one email address.'
end
class UserEmail < ActiveRecord::Base
belongs_to :user
validates_presence_of :user
validates_presence_of :email_address
end
[40] pry(main)> u = User.create!(name: 'doh')
(0.1ms) begin transaction
(0.0ms) rollback transaction
ActiveRecord::RecordInvalid: Validation failed: Emails User should have at least one email address.
[1] pry(main)> u = User.new(name: 'nice')
=> #<User id: nil, name: "nice", created_at: nil, updated_at: nil>
[2] pry(main)> u.emails.build(email_address: 'hello#world.blah')
=> #<UserEmail id: nil, user_id: nil, email_address: "hello#world.blah", created_at: nil, updated_at: nil>
[3] pry(main)> u.save!
(0.1ms) begin transaction
SQL (0.5ms) INSERT INTO "users" ("created_at", "name", "updated_at") VALUES (?, ?, ?) [["created_at", "2014-07-26 10:14:52.184245"], ["name", "nice"], ["updated_at", "2014-07-26 10:14:52.184245"]]
SQL (0.2ms) INSERT INTO "user_emails" ("created_at", "email_address", "updated_at", "user_id") VALUES (?, ?, ?, ?) [["created_at", "2014-07-26 10:14:52.189757"], ["email_address", "hello#world.blah"], ["updated_at", "2014-07-26 10:14:52.189757"], ["user_id", 5]]
(0.7ms) commit transaction
=> true
[4] pry(main)> u.emails.count
(0.2ms) SELECT COUNT(*) FROM "user_emails" WHERE "user_emails"."user_id" = ? [["user_id", 5]]
=> 1
[5] pry(main)> u.id
=> 5
[6] pry(main)> u.name
=> "nice"
[7] pry(main)> u.emails
=> [#<UserEmail id: 4, user_id: 5, email_address: "hello#world.blah", created_at: "2014-07-26 10:14:52", updated_at: "2014-07-26 10:14:52">]
[8] pry(main)> u
=> #<User id: 5, name: "nice", created_at: "2014-07-26 10:14:52", updated_at: "2014-07-26 10:14:52">
[19] pry(main)> UserEmail.create!(email_address: 'test#test.org')
(0.1ms) begin transaction
(0.1ms) rollback transaction
ActiveRecord::RecordInvalid: Validation failed: User can't be blank
Hope this helps.
I just realized that I adding a NOT NULL constraint on the storage layer is sufficient. I can remove the :user_id presence validation and have the same consistency behavior, with the exception that it now works.
However this looks quite hacky from the rails POW. Any better solution is still really appreciated...
I have a class as follows:
class Player < ActiveRecord::Base
attr_accessor :name, :rating, :team_name
end
So I ran 'bundle exec rails console' and then
Player.create(:name => "Ben", :rating => 5, :team_name => "Brown")
However, this is what I get back:
(0.1ms) begin transaction
SQL (7.0ms) INSERT INTO "players" ("created_at", "updated_at") VALUES (?, ?) [["created_at", Sat, 29 Mar 2014 04:24:31 UTC +00:00], ["updated_at", Sat, 29 Mar 2014 04:24:31 UTC +00:00]]
(0.9ms) commit transaction
=> #<Player id: 1, name: nil, rating: nil, team_name: nil, created_at: "2014-03-29 04:24:31", updated_at: "2014-03-29 04:24:31">
Why does the object that's created not have the properties I've assigned it? This strikes me as really strange...
Any guidance on this one?
Thanks,
Mariogs
Player.create(:name => "Ben", :rating => 5, :team_name => "Brown")
Here you are mass assigning attributes name, rating and team_name
So you need to tell Player model that above attributes can be mass assigned.
to allows mass assignment, you have to pass those attributes to attr_accessible method.
attr_accessor is used for creating getter and setter methods.
And getter and setter methods for database attributes are dynamically created, you don't need to create it.
so change
attr_accessor :name, :rating, :team_name
to
attr_accessible :name, :rating, :team_name
Using Rails 3.2.11 and ruby 1.9.3:
I have Review, User and ReviewAccess classes
class ReviewAccess < ActiveRecord::Base
belongs_to :user
belongs_to :review
attr_accessible :role_id
end
class Review < ActiveRecord::Base
has_one :review_accesses_owner, :class_name => 'ReviewAccess',
:conditions => "review_accesses.role_id = 1"
has_one :owner, :class_name => 'User', :through => :review_accesses_owner,
:source => :user
end
Basically Review is many-to-many with User and the join table is ReviewAccess where it additionally holds the relation role (1 for owner) in role_id column.
I can read the the review owner by:
Review.owner # works
# sql: SELECT "review_accesses".* FROM "review_accesses" WHERE "review_accesses"."review_id" = 7 AND (review_accesses.role_id = 1) LIMIT 1
However, setting the owner does not work because it doesn't set role_id to 1 (as stated in the conditions clause of the association)
Review.owner = current_user # does not set role_id
# sql: INSERT INTO "review_accesses" ("created_at", "review_id", "role_id", "updated_at", "user_id") VALUES ($1, $2, $3, $4, $5) RETURNING "id" [["created_at", Sun, 27 Oct 2013 08:02:54 UTC +00:00], ["review_id", 7], ["role_id", nil], ["updated_at", Sun, 27 Oct 2013 08:02:54 UTC +00:00], ["user_id", 1]]
I know I can override owner= but I have many of these (for each role) and I want to use the association DSL instead.
How to update associations having conditions to set the conditions on creation?
I'm a little bit afraid that using DSL it's not a good choice.
Prefer overriding accesorrs than putting code into callbacks if you need add some custom behavior.
To some generic, repetitive methods use metaprogramming stuff. If you'll have many roles you'll have DRY in your association definitions still, so problem is the same but in different place.
To sum up, override writer by using metaprogramming for it. Anywhere you have an array of roles, right?
(I could probably think of a better title for this and open to suggestions)
I am trying to call this in my NotesController, testing it in rails console:
?> u = User.first
User Load (1.6ms) SELECT "users".* FROM "users" LIMIT 1
=> #<User id: 2, email: nil, password: nil, linkedin_url: nil, created_at: "2012-06-17 05:54:44", updated_at: "2012-06-17 05:54:44", liid: "7fB-pQGIQi">
>> c = u.contacts.first
Contact Load (2.0ms) SELECT "contacts".* FROM "contacts" WHERE "contacts"."user_id" = 2 LIMIT 1
=> #<Contact id: 8, user_id: 2, created_at: "2012-06-19 01:23:45", updated_at: "2012-06-19 01:23:45">
>> c.notes.create!(:body => "this is a note")
(0.5ms) BEGIN
SQL (23.3ms) INSERT INTO "notes" ("body", "contact_id", "created_at", "updated_at", "user_id") VALUES ($1, $2, $3, $4, $5) RETURNING "id" [["body", "this is a note"], ["contact_id", 8], ["created_at", Thu, 21 Jun 2012 05:42:21 UTC +00:00], ["updated_at", Thu, 21 Jun 2012 05:42:21 UTC +00:00], ["user_id", nil]]
(1.7ms) COMMIT
=> #<Note id: 4, created_at: "2012-06-21 05:42:21", updated_at: "2012-06-21 05:42:21", body: "this is a note", user_id: nil, contact_id: 8>
The problem is in the last line where it says that the Note created has a "user_id: nil". I'm wondering what I'm missing that is not allowing the note to properly get the user_id from the contact user_id? I can think of a quick fix, to set the user_id on the note object, but it seems as though it could fetch it from the contact_id, am I wrong?
Here are my models in case this is helpful:
class Contact < ActiveRecord::Base
attr_accessible :user_id
belongs_to :user
has_many :notes
end
class User < ActiveRecord::Base
attr_accessible :password, :liid
has_many :contacts
has_many :notes, :through => :contacts
end
class Note < ActiveRecord::Base
attr_accessible :title, :body
belongs_to :contact
belongs_to :user
end
Thanks for the help!
Your Note also needs to belong to your User:
class Note < ActiveRecord::Base
attr_accessible :title, :body
belongs_to :contact
belongs_to :user
end
Rails will only automatically set the foreign_key of the parent object, and not the parent object's parent like you want. So you'll have to set that attribute manually:
c.notes.create!(:body => "this is a note", :user_id => c.user_id)