Rails 4 delete association for empty nested attributes when updating parent - ruby-on-rails

I have a model House that belongs to a User. House has_many cars. In my models, i've declared the necessary accepts_nested_attributes_for, and my nested attributes have the appropriate _attributes and id fields.
I want to be able to disassociate my Car model from House, but not User.
Let's say I have a saved House object like this:
"house" => {"owner" => "bob, "car_attributes" => [{"id" => 1, "type" => "toyota"},{"id" => 2, "type" => "honda"}]}
I want to remove (but not delete) a car object from House and update it so it looks like this:
"house" => {"owner" => "bob, "car_attributes" => [{"id" => 1, "type" => "toyota"}]}
Obviously when I call #house.update_attributes({"owner" => "bob, "car_attributes" => [{"id" => 1, "type" => "toyota"}]}), the belongs_to association is not removed. Is there any simple way to disassociate a car object from its parent?
Looking through the Rails documentation, there is a collection method delete that does just that -i.e sets the parent foreign keys to null. The problem is, I would have to mark each object I want to dissassociate in the client, send it over, then walk through each nested attribute and call delete() manually where necessary, which doesn't seem very railsy to me.
Is there an easier way to update a parent model by disassociating (without destroying) its nested attributes in rails?

Related

Is there any way that ruby's `create!` can make multiple objects at once?

I have a system that occasionally spits out 2 objects. Is there any magic to create! that would allow it to create two objects? E.G. if I say
self.class.create! make_up_attributes
and make_up_attributes passes a 2 item hash, can this create 2 objects of type self?
(note, create is probably an ActiveRecord method, in Rails)
Yep, simply pass in an Array of attribute hashes, like so:
self.class.create!([{:name => "John", :age => 26},
{:name => "Fred", :age => 50}])
See the docs

Adding more than one object to a serialized text column

I'm interested in adding two objects to a serialized meta-data column in my activity feed model (Rails 3.1) to cut down on db calls.
Example: I have an Activity model with a data:text column which is serialized. I know I can add a Book object to this model and get it back as so:
test = Activity.create(:data => Book.find(1))
test.book.author # => James Joyce
Can I add two objects to this column (e.g. a Book and a User)? I tried using hashes/arrays but couldn't get them to work properly. Thanks in advance.
Here's how you might use a hash:
test = Activity.create(:data => {:book => Book.find(1), :user => User.find(1)})
test.data[:book] # => #<Book id:1 ...>
test.data[:user] # => #<User id:1 ...>

Polymorphic Relationship Table Queries in Rails — find object by multiple

I have a relationship table in a rails application called edit_privileges, in which the User is the "editor" and a number of other classes are "editable". Let's say that two of those classes are Message and Comment.
My EditPrivilege model uses the following code:
belongs_to :editor, :class_name => "User"
belongs_to :editable, :polymorphic => true
And User, of course
has_many :edit_privileges, :foreign_key => "editor_id"
In order to determine if a user has edit privileges for a certain model, I can't do the normal query:
user.edit_privileges.find_by_editable_id(#message.id)
because if the user has edit privileges to edit a comment with the same id as #message, the query will return true with the wrong edit privilege record from the table.
So, I tried doing these options:
user.edit_privileges.find(:all, :conditions => ["editable_id = ? AND editable_type ?", #message.id, #message.class.to_s])
user.edit_privileges.where(:editable_id => #message.id, :editable_type => #message.class.to_s)
which works great at finding the right record, but returns an array instead of an object (an empty array [] if there is no edit privilege). This is especially problematic if I'm trying to create a method to destroy edit privileges, since you can't pass .destroy on an array.
I figure appending .first to the two above solutions returns the first object and nil if the result of the query is an empty has, but is that really the best way to do it? Are there any problems with doing it this way? (like, instead of using dynamic attribute-based finders like find_by_editabe_id_and_editable_type)
Use find(:first, ...) instead of find(:all, ...) to get one record (note it might return nil while find will raise an RecordNotFound exception). So for your example:
user.edit_privileges.find(:first, :conditions => { :editable_id => #message.id, :editable_type => #message.class.to_s })
BTW, if you're on more edge rails version (3.x), Model.where(...).first is the new syntax:
user.edit_privileges.where(:editable_id => #message.id, :editable_type => #message.class.to_s).first

Rails 3 - is possible to make IFs in a database query

I have a profile card of user that have registration in my forum.
Person.update_all({:name => params[:person][:name],
:sex => params[:person][:sex],
:age => params[:person][:age],
:avatar => params[:person][:avatar].original_filename,
:city => params[:person][:city]},
{:id => params[:id]})
This is query for updating data in database. But here is a small problem - this will work only in a situation, if I the user send through form avatar (image). If not send avatar - that means the user already have uploaded avatar and the form send only name, sex, age and city. So in this case I'll get error in line :avatar => params[:person][:avatar].original_filename, -- and I would like to ask you for, if exist some elegant way, how to treat this moment.
I thought something like this:
if params[:person][:avatar]
avatar = ':avatar => params[:person][:avatar].original_filename,'
end
Person.update_all({:name => params[:person][:name],
:sex => params[:person][:sex],
:age => params[:person][:age],
avatar
:city => params[:person][:city]},
{:id => params[:id]})
But unfortunately, this doesn't work... How you're solving similar situation?
Thank you.
Well, it seems, like your params[:person] keys are similar to your model fields. So why don't you just pass params[:person] to update_all?
Alternatively, you could create a hash person, initialize it the way you want and then pass it to update_all
person = { :name => params[:person][:name] ,
...
if params[:person][:avatar]
person[:avatar] = params[:person][:avatar].original_filename
end
Person.update(params[:id], person)
I've changed update_all to update, because update_all is used to update all the records (that match the condition), while update find's the record by it's ID.
But again, it's a bad practice and you have to type a lot of unnecessary code.
One more thing. update_all makes a direct DB call, which doesn't involve validations, callbacks etc.
So, if you don't have some special reason for this, you'd better do something like this:
#person = Person.find params[:id]
#person.update_attributes params[:person]
I really think, you should check this book out
Updated once again :)
You see, such things belong to your models, not controllers. You could define a setter in the model:
def avatar=(value)
write_attribute(:avatar, value.original_filename)
end

before_filter or rescue to create a new record

I've 2 models
class Room < ActiveRecord::Base
has_many :people
accepts_nested_attributes_for :people, :reject_if => lambda { |a| a[:person_id].blank? }, :allow_destroy => true
end
class Person < ActiveRecord::Base
belongs_to :room
end
In '/rooms/new' form I've a select tag containing all Person + an 'other' option tag that allow the user to add dynamicaly a person to the select tag (New name).
So, when I submit my form I can have a person with id = -1 which doesn't exist in database, and of course, I want to create a new Person with the new name.
I'm wondering what is the best way to achieve that?
with a 'before_filter' or a 'rescue ActiveRecord::RecordNotFound' or ...
thanks for your help
As a general practice, I would not suggest using exception handling as a control for functional logic. So I am advocating for checking for an id of -1, and creating the person in that case, rather than doing so after the fact in a rescue block.
If you are looking for a reason, 2 I think about are performance and clarity.
Exceptions are expensive, and you don't want to incur the processing cost for them if it can be avoided.
Also, exceptions are meant to indicate an error condition, not an expected path in your logic. By using them this way, you are muddying the waters, and making it seem like this is not meant to work his way. By having the check for a non-extant person in the before filter, it is more clear this is supposed to happen sometimes, and clear that this happens before the rest of the save.
Also, if you did this logic in handling the exception, you then have to retry the operation that failed, making your save logic that much more complex by either looping or being recursive or otherwise duplicating the failing save. That will also make your code less clear to the next coder that has to work on it.
You don't need any special code. ActiveRecord already includes logic to handle this case.
Read the rdoc at http://github.com/rails/rails/blob/2-3-stable/activerecord/lib/active_record/nested_attributes.rb#L328 or http://github.com/rails/rails/blob/master/activerecord/lib/active_record/nested_attributes.rb#L332 for the details. Essentially, if the Hash is passed with an :id key that record's attributes are updated. If the record has no :id key, a new record is created. If it has an :id key, and a :_destroy key with a true'ish value, the record will be deleted.
Below are the 2-3-stable branch documentation:
# Assigns the given attributes to the collection association.
#
# Hashes with an <tt>:id</tt> value matching an existing associated record
# will update that record. Hashes without an <tt>:id</tt> value will build
# a new record for the association. Hashes with a matching <tt>:id</tt>
# value and a <tt>:_destroy</tt> key set to a truthy value will mark the
# matched record for destruction.
#
# For example:
#
# assign_nested_attributes_for_collection_association(:people, {
# '1' => { :id => '1', :name => 'Peter' },
# '2' => { :name => 'John' },
# '3' => { :id => '2', :_destroy => true }
# })
#
# Will update the name of the Person with ID 1, build a new associated
# person with the name `John', and mark the associatied Person with ID 2
# for destruction.
#
# Also accepts an Array of attribute hashes:
#
# assign_nested_attributes_for_collection_association(:people, [
# { :id => '1', :name => 'Peter' },
# { :name => 'John' },
# { :id => '2', :_destroy => true }
# ])

Resources