When using the new accepts_nested_attributes_for in ActiveRecord, it's possible to use the option :allow_destroy => true. When this option is set, any hash containing nested attributes like {"_delete"=>"1", "id"=>"..."} passed to update_attributes will delete the nested object.
Simple setup:
class Forum < ActiveRecord::Base
has_many :users
accepts_nested_attributes_for :users, :allow_destroy => true
end
class User < ActiveRecord::Base
belongs_to :forum
end
Forum.first.update_attributes("users_attributes"=>{"0"=>{"_delete"=>"1", "id"=>"42"}})
Question: How do I - instead of deleting the nested objects when "_delete" => "1" - just remove the association? (i.e. In the above case set the forum_id on the user to nil)
Bonus question: What if I also want to change the an attribute on the nested object when removing the association? (e.g. like setting a state or a timestamp)
Instead of asking for the user to be deleted using "_delete" => '1', can you not just update it using the nested_attributes?:
Forum.first.update_attributes("users_attributes"=> {
"0" => {
"id" => "42",
"forum_id" => "",
"state" => 'removed'
}
})
Related
I'm use of Ruby on Rails.
My models like this.
Weblog model
class Weblog < ActiveRecord::Base
attr_accessible :body
has_many :comments, :dependent => :destroy
accepts_nested_attributes_for :comments
end
Comment model
class Comment < ActiveRecord::Base
attr_accessible :comment
belongs_to :weblog
end
My console log is here
$ attr = {}
$ attr["blog"] = {"body" => "test body", "comments_attributes" => {"0" => {"comment" => "comment1"}, "1" => {"comment" => "comment2"}}}
$ blog = Weblog.new(attr["blog"])
$ blog.save #=> comment data are saved with id 1 and 2 in comments table
$ attr["blog"] = {"body" => "update test body", "comments_attributes" => {"0" => {"comment" => "commentA", "id" => "1"}}} # I want to delete comment data whose id is 2 in comment table
$ blog2 = Weblog.first
$ blog2.update_attributes(attr["blog"]) #=> updates are correctly finished..
But the data whose id is 2 in comment table are not deleted.
How to delete comments table data through updating weblogs table.
You need to add allow_destroy => true option to your accepts_nested_attributes_for call:
class Weblog < ActiveRecord::Base
attr_accessible :body
has_many :comments, :dependent => :destroy
accepts_nested_attributes_for :comments, allow_destroy: true
end
You need to use the :allow_destroy option when declaring the model accepts nested attributes. After you do that, when you want to remove a nested model you need to pass the id of the model and the special attribute _destroy with value '1'
You can see the doc and an example at http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
I am trying to send a POST request to a rails scaffold controller which contains a nested array representing records that need to be created and associated with the newly created parent.
Here is some example JSON:
{
"plan_id":3,
"weight":60,
"exercise_sets": [
{
"created_at":"2012-06-13T14:55:57Z",
"ended_at":"2012-06-13T14:55:57Z",
"weight":"80.0",
"repetitions":10,
"exercise_id":1
}
]
}
..and my models..
class Session < ActiveRecord::Base
has_many :exercise_sets, :dependent => :destroy
has_many :exercises, :through => :exercise_sets
end
class ExerciseSet < ActiveRecord::Base
belongs_to :exercise
belongs_to :session
end
Is what I am attempting possible?
This is certainly not impossible, though you may have to switch up your parameter naming a bit.
When you pass the JSON above to the controller, it either gets passed as parameters to a constructor:
Session.new(params[:session])
Or gets passed to the #update_attributes method on a persisted Session instance:
#session = Session.find(params[:id])
#session.update_attributes(params[:session])
Both the constructor and #update_attributes methods turn parameters like "plan_id" into assigment method calls. That is,
#session.update_attributes(:plan_id => "1")
Turns into (inside the #update_attributes method):
#session.plan_id = "1"
So, this works for your plan_id and weight attributes, because you have both #plan_id= and #weight= setter methods. You also have an #exercise_sets= method given to you by has_many :exercise_sets. However, the #exercise_sets= method expects ExerciseSet objects, not ExerciseSet attributes.
Rails is capable of doing what you are trying to do via the #accepts_nested_attributes_for class method. Try this:
class Session < ActiveRecord::Base
has_many :exercise_sets, :dependent => :destroy
has_many :exercises, :through => :exercise_sets
accepts_nested_attributes_for :exercise_sets
end
This sets up (metaprograms) an #exercise_sets_attributes= method for you. So just modify your JSON to:
{
"plan_id":3,
"weight":60,
"exercise_sets_attributes": [
{
"created_at":"2012-06-13T14:55:57Z",
"ended_at":"2012-06-13T14:55:57Z",
"weight":"80.0",
"repetitions":10,
"exercise_id":1
}
]
}
More info: http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
It's perfectly possible, you just have to add the following line to your Session model:
accepts_nested_attributes_for :exercise_sets, :reject_if => lambda { |a| a[:exercise_id].blank? }, :allow_destroy => true
Now, when your controller does Session.new(params[:session]), Rails will build (or update) the Session and the related ExerciceSet(s).
Please review the :reject_if clause. That's where you define which records will be created and which will not.
This are the basics, but Ryan Bates explains the nested model forms perfectly (as always) in this screencast.
Is there any way to halt saving of child before parent.
I am using accepts_nested_attributes_for with polymorphic association.
I used multiple options validates_presence_of :parent_id , validates_assoicated :parent but none are working.
For example, I do have a class
Class Person
include HasPhoneNumbers
..
end
module HasPhoneNumbers
def self.included(kclass)
kclass.has_many :phone_numbers, :as => :callable, :dependent => kclass == Person ? :destroy : :nullify
end
klass.accepts_nested_attributes_for :phone_numbers, :reject_if => lambda {|pn| pn.keys.any?{|k| k.to_sym != :id && pn[k].blank?} }
end
class PhoneNumber
belongs_to :callable, :polymorphic => true
end
So while saving person due to validation in person object, it was not saving. However, child(phone_number) was saving. So I need to restrict it to not save child(phone_number) before parent(person) saves.
I did try multiple options using validates_presence_of and validates_associated, but none are working for me.
#person = Person.new(params[:person])
ActiveRecord::Base.transaction do
person.save!
end
Wrapping your saves within a transaction should roll back the phone number save if the person fails validation.
Reference: ActiveRecord Transactions
Is there such a thing as .current in Ruby/Rails?
I have the following in my Release model to accept tracks as nested attributes. I'm using :after_add to manually set the position column in the has_many through join table. I ideally want this to be populated from either the position attribute sent from the fields_for part of my form or copied from the value set in the tracks table/model on save.
I can get it to set the first or last positions on all entries, but not the current position that relates to that track?
I ideally need releases_tracks.each { |t| t.position = self.tracks.last.position } to be something like releases_tracks.each { |t| t.position = self.tracks.current.position }
has_many :releases_tracks, :dependent => :destroy, :after_add => :position_track
has_many :tracks, :through => :releases_tracks, :order => "position"
accepts_nested_attributes_for :tracks, :reject_if => lambda { |a| a[:name].blank? }, :allow_destroy => :true
accepts_nested_attributes_for :releases_tracks
def position_track(track)
releases_tracks.each { |t| t.position = self.tracks.last.position }
end
Can anyone help?
Can't say for sure if I understood you correctly, but, as far as I can tell, releases_tracks.each { |t| t.position = t.track.position } should solve your problem.
belongs_to - has_many relationship works two ways, so for two models «Owner» and «Belonging» bound by such relationship both Owner.first.belonging and Belonging.last.owner queries are valid.
Hi i have these classes:
class Core < ActiveRecord::Base
belongs_to :resource, :polymorphic => true
belongs_to :image, :class_name => 'Multimedia', :foreign_key => 'image_id'
end
class Place < ActiveRecord::Base
has_one :core, :as => :resource
end
If i try do launch this:
a = Place.find(5)
a.name ="a"
a.core.image_id = 24
a.save
name is saved. image_id no
i want save automatically all changes in records in relationship with place class at a.save command. is possible?
thanks
Use :autosave => true
See section titled One-to-many Example for ActiveRecord::AutosaveAssociation.
You'll want something like:
class Place
has_one :core, :as => :resource, :autosave => true
end
Disclaimer:
The :autosave => true should be used on the "parent" Object. It works great with has_one and has_many, but I've run into great difficulty attempting to use it on a belongs_to. relationship.
I think that you can use the build_association method to do that. For example,
a = Place.find(5)
a.name = "a"
a.build_core(:image_id => 24)
a.save
But it might only work if the place object was created before hand.