How do I call a validation on a child update in Rails? - ruby-on-rails

If I have two models, a User and a Post model where they are related with a has-many association (a user has many posts), can I create a validation that that triggers whenever either the User or its associated posts attributes gets changed?
i.e. We want a user to have a validation that is triggered whenever a user attribute gets updated or when one of its posts gets updated/created.

You can use validates_associated to trigger the associations on an associated model:
class User < ApplicationRecord
validates :name, presence: true
has_many :posts
end
class Post < ApplicationRecord
belongs_to :user
validates_associated :user
end
user = User.create!(name: 'Max')
post = user.posts.new
user.name = ''
post.save! # will trigger a validation error
can I create a validation that that triggers whenever either the User
or its associated posts attributes gets changed?
This is not how validations work. Validations are fired when you call .valid?, .save/save! or .update/.update! on the model. This sounds more like a normal callback or an assocation callback or a X&Y problem.

Yes, you can! You got some special functions related to lifecycle of your models.
Check here!
before_create do
** whatever you want **
end

Related

Rails: handling object creation via candidate key

I have a table foo which belongs_to :bar (bar has_many :foos).
There are too many foos for it to be sensible to offer the user a list when they're creating a bar, so I'm going to get the user to enter the name of the bar that the foo belongs to. The name is a candidate key -- guaranteed to be present and unique.
I think I can see the steps I need to take: make foo validate that bar_id is present, and somewhere put a find_where to look up the bar that has the given name (if it's not present I'll get a nil, so the validation should fail -- good).
What I want to know is where's the best place to put that find_where in order to check all creations and edits of foo whilst maintaining DRY principles? How to I join it up to the new and edit forms? (Or is there a more Rails way?)
Rails 4, by the way.
EDIT:
To make it more concrete, suppose I have a list of every airport in the world, in Airport, which contains the airport name (name), the 3-character identifier that gets put on your luggage tag (code), and the Rails-generated airport_id. And suppose I have Departure which belongs_to :airport. When I create/edit a departure, the user enters the airport code, which I have to map to an actual entry in Airport to create/edit the entry. What's the best place/way to do that?
You don't actually need to do all of that, Rails has a way! When you specify an association between two models in your database, it suffices to simply add the dependent model name to a validation:
class Foo < ActiveRecord::Base
belongs_to :bar, validate: true
end
This will cause ActiveRecord to validate the presence of the bar object in question. And, because this is Rails, there are other ways of writing this type of validation:
With validates_associated
class Foo < ActiveRecord::Base
belongs_to :bar
validates_associated :bar
end
or with validates presence
class Foo < ActiveRecord::Base
belongs_to :bar, inverse_of :foo
validates :bar, presence: true
end
class Bar < ActiveRecord::Base
has_many :foo, inverse_of :bar
end
Beware a couple gotchas:
In the validates_associated construction, you must make sure to only specify the validation on one of your two related models. If you specify the validates_associated attribute on both, your validations will call each other infinitely.
In the validates presence version, you must specify the inverse_of relationship in order to validate the presence of the associated AR model.
EDIT
In response to your edited question, I've updated my answer:
You'll want to query the Airport by its code attribute in your DeparturesController, under the #create and #update actions:
class DeparturesController < ApplicationController
def create
airport = Airport.find_by(code: params[:departure][:code])
if airport
departure = airport.departures.build(params[:departure])
if departure.save
...
else
...
end
else
...
end
end
end
The upshot of using the find_by method in ActiveRecord is that it returns nil if the search fails, making it easy for you to write error-handling logic in the event that your user enters an invalid code.

Correct ROR model callback for creation associated

I have associated models User and Channel in Rails 3 app. Channel is created at the moment of User creation
class User < ActiveRecord::Base
before_create do
self.channels.build
end
has_many :channels
end
class Channel < ActiveRecord::Base
belongs_to :user
validations block
...
end
Problem is that if validations for Channel will not pass User will be created at DB but Channel won't. In what callback place Channel creation to create User and Channel in one 'transaction'? Or, maybe, there is another right way?
Thanks in advance.
UPD1:
Channel autocreate on User create placed in model because in some cases objects created not invoking controllers.
You can use "accepts_nested_attributes_for"
class User < ActiveRecord::Base
has_many :channels
accepts_nested_attributes_for :channels
end
class Channel < ActiveRecord::Base
belongs_to :user
validations block
end
You think too much. This is very common case and has a convention.
Firstly at Pedro said, you need a validation of association in Channel model. This will prevent saving of channel without user_id.
Then, in controller's create action, you just make sure all params including user object is sent here for creation.
Use validates :channels, associated: true.
You should probably review your Channel validations because if it is not saving, you're doing something your app doesn't expect.

after_create callback: flash message

After a user signs up to my Rails 3.2.3 app which uses Devise, an after_create callback is triggered which attempts to create an Organisation record.
If this create fails validation, how can I get the error messages to display? Should I override the devise controller? Thing is, the errors are related to the creation of an Organisation, not a User (and I have a pretty vanilla devise User model set-up).
I'm doing something like this in the User model:
# after_create callback method
...
unless new_org_user.save
errors.add("Warning", "Organisation is invalid" )
end
...
Since you are firing the event in the after_create callback the record is already successfully created so no flash message will apear.
Why dont you try to move it to relation association instead and require validation of child like
class User < ActiveRecord::Base
has_one :organization
accepts_nested_attributes_for :organization
end
class Organization < ActiveRecord::Base
belongs_to :user
end
This way if organization is not valid it will fail the validation

Rails - execution sequence of after create callback & nested attributes

I have a simple set up of User and UserProfile model with User has_one :user_profile and UserProfile belongs_to :user.
But I am unable to wrap my head around how Rails defines execution order of after_create callback and accepts_nested_attributes_for defined in my model. Lets consider these two cases.
Case 1:
class User < ActiveRecord::Base
has_one :user_profile
accepts_nested_attributes_for :user_profile
after_create :test_test
end
Now, if I create a user(with user_profile_attributes hash too) via the console, the after_create callback is triggered after the user and its user profile is created.
Case 2:
If the after_create is placed at the top,
class User < ActiveRecord::Base
after_create :test_test
has_one :user_profile
accepts_nested_attributes_for :user_profile
end
the callback is triggered after a user has been created but before creating a user profile.
Is this the way it is expected to function. What does Rails do internally here? Is the execution sequence simply determined by the order of the code?
Where do I start to dig deeper into or debug this ?
The order of the declarations in your model can have an impact on the execution order of the code. This is a source for various weird things. (for example, currently callback definitions and has_and_belongs_to_many associations are order dependent: https://github.com/rails/rails/pull/8674 )
To debug the issue you need to browse the rails source. Since your problem has to do with execution order, callbacks and nested attributes I would start by reading up on:
https://github.com/rails/rails/blob/master/activerecord/lib/active_record/nested_attributes.rb#L256
https://github.com/rails/rails/blob/master/activerecord/lib/active_record/callbacks.rb#L302
https://github.com/rails/rails/blob/master/activemodel/lib/active_model/callbacks.rb#L98
This gives you the necessary background to dig deeper. You'll notice that accepts_nested_attributes_for calls into add_autosave_association_callbacks https://github.com/rails/rails/blob/master/activerecord/lib/active_record/autosave_association.rb#L173
This method adds an after_create callback and as far as I know callbacks are executed in order of definition.

Set default random column value in ActiveRecord model

got 2 models:
class User < ActiveRecord::Base
has_many :posts
end
and
class Post < ActiveRecord::Base
belongs_to :user
end
the Posts table has a column: u_hash. This is supposed to be a randomly generated identifying hash (for public viewing). What is the best way to generate this hash and how can I add it to the table? The idea is that all this will happen in the background and not be visible to the user (no hidden field in the form). The database used is MySQL if that could help me out somehow.
Thanks in advance!
J
You most likely need before_validation_on_create callback for your Post model. This callback is internally called by ActiveRecord functionality when you save a new Post record into database.
A good callback reference and a hint of the order callbacks are called in you can find here.
Here's a code, that explains why it is before_validation_on_create that you need to use:
class Post < ActiveRecord::Base
belongs_to :user
validates_uniqueness_of :u_hash
before_validation_on_create :generate_u_hash
def generate_u_hash
begin
new_u_hash = "random hash here"
end while Post.find_by_u_hash(new_u_hash)
self.u_hash = new_u_hash
end
end
This sounds like a job for ActiveRecord callbacks.
If your posts tables has a before_create callback, you can create and set a value automatically every time a new post instance is created.
e.g.:
class Post < ActiveRecord::Base
belongs_to :user
before_create :set_uhash_column
private
def set_uhash_column
#your code here - something like self.uhash = ...
end
end

Resources