I have 2 models - User and Alias, where User has_many :aliases.
When creating a User, it must be unique to any Alias that already exists.
When a User is created, they also get an Alias saved with that User's name.
Here is the code for my User.rb model
validates_associated :aliases # bring in any validations from relationship models
before_create :create_alias
def create_alias
a = self.aliases.new
a.alias = username
return a.save
end
The alias model validation is validates_uniqueness_of :alias.
My theory is, that before I create a User model go create an Alias model and if that fails, then creating the User model should also fail.
However, when it fails, rails is exploding.
It's not doing the validates_associated properly.
How can I accomplish what I want to do?
check this http://guides.rubyonrails.org/association_basics.html#has-many-association-reference, you can use the :autosave and :validate options when you define the association so you can just remove that validates_associated and your before_create callback and let rails handle those actions
has_many :aliases
EDIT: I think the problem is that you are assigning new alias AFTER the validations! so, you have to build the new alias right when you assign the username of after validations
def username=(value) #custom setter for username
aliases.build(alias: value)
write_attribute(:username, value)
end
now the alias will be there before the validations and user.valid? will run the alias' valdations too when validating
This must be impossible to do in a model. At least with devise...
To fix this problem I just gave up trying and moved the config to the controller with something like this:
#user.aliases.build(:name => #user.username)
Related
I would like to know if there is a way in rails to validate the presence (AND existence in the database) of an association without using any additional gem if possible (i.e the associated "belongs_to" object is in the database and valid before saving).
validates_presence_of does not work because you can use a newly created and unsaved object.
I know all about the validates_existence gem but I would like to avoid it if possible.
You can use validates_associated in conjunction with validates_presence_of.
validates_associated will run validations on the associated model (which you have to define in that model).
validates_presence_of validates presence of the association.
EDIT
Addressing your comment: not a common scenario to validate presence in db specifically, but you can do this:
validate :association_exists
def association_exists
# query database for the association record and return true if it exists
# self is model instance inside this method
end
I have a Record model and in order to edit this model, you must be logged in as an instance of Admin. I would like to have a column called last_modified_by which points to the Admin who last modified the Record. In the database, I was thinking it would be good in the records table to add a column that holds the Admin's id; however, the only way I know how to do that is with an association. These two models are not associated with each other so an association doesn't make a lot of sense. Is there any other way I might be able to accomplish this task without resorting to associations? Any advice would be much appreciated!
Hmm, I think the association is a good tool here. You might want to try to hack it somehow but I think nothing you can conjure up will ever be as good as an association via a foreign_key(also so fast). But perhaps you would like to name your association and do something like:
class Record < ActiveRecord::Base
belongs_to :culprit, :class_name => 'Admin', :foreign_key => 'last_modified_by'
end
or give it some more senseful naming?
You could create an Active Record before_save callback. The callback would save the admin's id into the last_modified_column. This would make sure the admin id is saved/updated each time there is a change to the model.
For example, assuming admin is #admin:
class Record < ActiveRecord::Base
before_save :save_last_modified
def save_last_modified
self.last_modified_column = #admin.id
end
As for getting #admin, you could employ a method similar to this, and set #admin = Admin.current (like User.current in the link) somewhere in the Record model.
How do I change an AcriveRecord from marked to be saved to make sure it does not get saved, from within the model itself?
Considering I can have a method run by a hook in activerecord, such as: before_save
for (hypothetical) example:
before_save :ignore_new_delete_exisiting_if_blank(self.attribute)
def ignore_new_delete_exisiting_if_blank(attribute)
self.do_not_save_me! if attribute.blank?
#what is that magic "do_not_save_me" method?
#Is there such thing, or something to achieve the same thing?
end
Update
My particular use case requires that no errors be thrown and other models to continue to be saved, even if this one will not. I should explain:
I am using model inheritance, and I am having an issue with figuring out how to let save the parent model, but if the child model instances are blank, (no values exist in certain attributes) they should not be persisted; however, the parent should still be persisted. This scenario does not let me make use of validations on the child model as that would block the parent from being persisted as well...
Your method should just return false to make it does not save.
Or you set the errors, which will allow to be more descriptive.
For example:
def ignore_new_delete_exisiting_if_blank_attribute
if attribute.blank?
errors.add(:base, "Not allowed to save if attribute is blank.")
end
end
Note that you cannot send parameters to a before_save. If you just want to make sure a record is not saved when an attribute is not present, you should use
validates_presence_of :attribute
[UPDATE]
When saving a parent model with children, you have to do something like accepts_nested_attributes_for, and in that call, you can specify which attributes must be given or when a child-record is ignored.
For example
accepts_nested_attributes_for :posts, :reject_if => proc { |attributes| attributes['title'].blank? }
will not save a post if the title is blank.
Hope this helps.
The "magic" is that when you return false from the method, the record won't be saved.
In your case:
def ignore_new_delete_exisiting_if_blank(attribute)
attribute.present?
end
What's the best practice to create has_one relations?
For example, if I have a user model, and it must have a profile...
How could I accomplish that?
One solution would be:
# user.rb
class User << ActiveRecord::Base
after_create :set_default_association
def set_default_association
self.create_profile
end
end
But that doesn't seem very clean... Any suggestions?
Best practice to create has_one relation is to use the ActiveRecord callback before_create rather than after_create. Or use an even earlier callback and deal with the issues (if any) of the child not passing its own validation step.
Because:
with good coding, you have the opportunity for the child record's validations to be shown to the user if the validations fail
it's cleaner and explicitly supported by ActiveRecord -- AR automagically fills in the foreign key in the child record after it saves the parent record (on create). AR then saves the child record as part of creating the parent record.
How to do it:
# in your User model...
has_one :profile
before_create :build_default_profile
private
def build_default_profile
# build default profile instance. Will use default params.
# The foreign key to the owning User model is set automatically
build_profile
true # Always return true in callbacks as the normal 'continue' state
# Assumes that the default_profile can **always** be created.
# or
# Check the validation of the profile. If it is not valid, then
# return false from the callback. Best to use a before_validation
# if doing this. View code should check the errors of the child.
# Or add the child's errors to the User model's error array of the :base
# error item
end
Your solution is definitely a decent way to do it (at least until you outgrow it), but you can simplify it:
# user.rb
class User < ActiveRecord::Base
has_one :profile
after_create :create_profile
end
If this is a new association in an existing large database, I'll manage the transition like this:
class User < ActiveRecord::Base
has_one :profile
before_create :build_associations
def profile
super || build_profile(avatar: "anon.jpg")
end
private
def build_associations
profile || true
end
end
so that existing user records gain a profile when asked for it and new ones are created with it. This also places the default attributes in one place and works correctly with accepts_nested_attributes_for in Rails 4 onwards.
Probably not the cleanest solution, but we already had a database with half a million records, some of which already had the 'Profile' model created, and some of which didn't. We went with this approach, which guarantees a Profile model is present at any point, without needing to go through and retroactively generate all the Profile models.
alias_method :db_profile, :profile
def profile
self.profile = Profile.create(:user => self) if self.db_profile.nil?
self.db_profile
end
Here's how I do it. Not sure how standard this is, but it works very well and its lazy in that it doesn't create extra overhead unless it's necessary to build the new association (I'm happy to be corrected on this):
def profile_with_auto_build
build_profile unless profile_without_auto_build
profile_without_auto_build
end
alias_method_chain :profile, :auto_build
This also means that the association is there as soon as you need it. I guess the alternative is to hook into after_initialize but this seems to add quite a bit of overhead as it's run every time an object is initialized and there may be times where you don't care to access the association. It seems like a waste to check for its existence.
There is a gem for this:
https://github.com/jqr/has_one_autocreate
Looks like it is a bit old now. (not work with rails3)
I had an issue with this and accepts_nested_attributes_for because if nested attributes were passed in, the associated model was created there. I ended up doing
after_create :ensure_profile_exists
has_one :profile
accepts_nested_attributes_for :profile
def ensure_profile_exists
profile || create_profile
end
If you need the has_one association to exist before saving the object (when testing, for instance), you should use the after_initialize callback instead. Here is how it could be applied to your use case:
class User < ActiveRecord::Base
has_one :profile
after_initialize :build_profile, unless: :profile
end
I am currently using nested model mass assignment on one of my models. It works a treat, however, I'd like to be able to ensure that any nested models that are created "belong_to" the same user.
I've managed to implement this by using alias method chaining with:
def contact_attributes_with_user_id=(attributes)
self.contact_attributes_without_user_id = attributes.merge( "user_id" => user_id )
end
alias_method_chain :contact_attributes=, :user_id
Now this works fine, but it means I can no longer have attribute protection on user_id for the contact - which could easily catch someone out in the future.
Can anyone come up with a better way?
What if you add a before_save hook to your Contact model, like this:
belongs_to :parent
validates_presence_of :parent_id
before_save :assign_user_id
private
def assign_user_id
self.user_id = parent.user_id
end
This way your Contacts' user_ids will follow the parent model's and you don't have to worry about assigning at all (you can get rid of the alias_method_chain).