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
Related
This is a rather simple Rails 4 situation. Model Intranet has_many activities. Activities exists with sufficient records for several intranets. Current_intranet.activities.size returns 69 records. However, whenever I try to access any of the records, I receive "output error: <ArgumentError: wrong number of arguments (1 for 0)" even though I'm not passing any arguments. All of the following fail with that error.
Current_intranet.activities.first
Activity.where(intranet_id: 1).first
Activity.where(:intranet_id => 1).first
Activity.where{intranet_id.eq 1}.first
All of the above with [0] instead of first
There is no problem with any other models
I'd appreciate any suggestions. Thanks in advance.
There are no defined scopes. The code is:
class Activity < ActiveRecord::Base
acts_as_capitalized
# activities created during intranet creation.
DEFAULT_ACTIVITIES = { 'Document Preparation' => 'general income', 'No Charge' => 'general income'}
# static activity names that can not be deleted.
STATIC_ACTIVITY_NAMES = [ 'Document Preparation','No Charge']
before_destroy :check_if_used
before_destroy :cannot_delete_static_activities
belongs_to :created_by_user, :class_name => "User", :foreign_key => "created_by"
belongs_to :updated_by_user, :class_name => "User", :foreign_key => "updated_by"
belongs_to :intranet
belongs_to :chart_of_account
has_many :activity_rates, :dependent => :destroy
has_many :event_type
has_many :billings do
def find_by_name(name)
(find :all, :conditions => ["activity.name = ?",name])
end
end
has_many :document_templates, :foreign_key => "billing_activity_id"
validates_presence_of :intranet_id, :name, :chart_of_account_id
validates_uniqueness_of :name, :scope => :intranet_id
Let's say you have a SiteUpdate and a Comment model, and you want to make Comment polymorphic. You make comment hold a "commentable_id" and "commentable_type"...
Here's our comment model:
class Comment < ActiveRecord::Base
belongs_to :commentable, :polymorphic => true
validates_presence_of :content
validates_presence_of :commentable
end
Here is our SiteUpdate:
class SiteUpdate < ActiveRecord::Base
belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
has_many :comments, :as => :commentable
validates_presence_of :subject
validates_length_of :subject, :maximum => 80
validates_presence_of :intro
validates_length_of :intro, :maximum => 200
validates_presence_of :text
validates_presence_of :author
scope :sorted, order("site_updates.created_at desc")
end
Does Rails link the commentable to the site_update instance, or do I have to do that manually?
#site_update.comments << Factory.build(:comment, :commentable_id => nil)
#site_update.save
This fails -> it complains that the comment.commentable_id should not be blank (I set this validation in the Comment model).
So do I do this manually, or do I set this up incorrectly?
Or do I just not validate it at all?
I'm making an assumption that your #site_update object is a new object. If so...
There's a somewhat annoying thing about rails associations. You can't really add them before the record is saved.
What's happening is, you have a new site update object without an ID. You build a new comment object for that site update, so it sets the commentable_type to "SiteUpdate", however, there's no ID yet, so it sets the commentable_id to nil. You save, and it bubbles out to save associated objects, but it doesn't set the comment commentable_id to the SiteUpdate ID, because it doesn't exist.
So if you change it around to be :
#site_update.save
#site_update.comments << Factory.build(:comment, :commentable_id => nil)
#site_update.comments.map { |c| c.save }
it should work.
If it's not a new record...it should work as is.
Similar to this question, how do I set a property on the join model just before save in this context?
class Post < ActiveRecord::Base
has_many :post_assets
has_many :assets, :through => :post_assets
has_many :featured_images, :through => :post_assets, :class_name => "Asset", :source => :asset, :conditions => ['post_assets.context = ?', "featured"]
end
class PostAssets < ActiveRecord::Base
belongs_to :post
belongs_to :asset
# context is so we know the scope or role
# the join plays
validates_presences_of :context
end
class Asset < ActiveRecord::Base
has_many :post_assets
has_many :posts, :through => :post_assets
end
I just want to be able to do this:
#post = Post.create!(:title => "A Post")
#post.featured_images << Asset.create!(:title => "An Asset")
# ...
#post = Post.first
#featured = #post.featured_images.first
#=> #<Asset id: 1, title: "An Asset">
#featured.current_post_asset #=> #<PostAsset id: 1, context: "featured">
How would that work? I've been banging my head over it all day :).
What currently happens is when I do this:
#post.featured_images << Asset.create!(:title => "An Asset")
Then the join model PostAsset that gets created never gets a chance to set context. How do I set that context property? It looks like this:
PostAsset.first #=> #<PostAsset id: 1, context: nil>
Update:
I have created a test gem to try to isolate the problem. Is there an easier way to do this?!
This ActsAsJoinable::Core class makes it so you can have many to many relationships with a context between them in the join model. And it adds helper methods. The basic tests show basically what I'm trying to do. Any better ideas on how to do this properly?
Look at the has_many options in the ActiveRecord::Associations::ClassMethods API located here: http://rails.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#M001316
This is the most interesting quote:
:conditions
Specify the conditions that the associated object must meet in order to be included as a WHERE SQL fragment, such as authorized = 1. Record creations from the association are scoped if a hash is used. has_many :posts, :conditions => {:published => true} will create published posts with #blog.posts.create or #blog.posts.build.
So I believe your conditions must be specified as a hash, like so:
class Post < ActiveRecord::Base
has_many :post_assets
has_many :featured_post_assets, :conditions => { :context => 'featured' }
has_many :assets, :through => :post_assets
has_many :featured_images, :through => :featured_post_assets,
:class_name => "Asset", :source => :asset,
end
And you should also do the following:
#post.featured_images.build(:title => "An asset")
instead of:
#post.featured_images << Asset.create!(:title => "An Asset")
This should call the scoped asset build, as suggested in the quote above to add the context field to asset. It will also save both the join model object (post_asset) and the asset object to the database at the same time in one atomic transaction.
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'
}
})
How do you edit the attributes of a join model when using accepts_nested_attributes_for?
I have 3 models: Topics and Articles joined by Linkers
class Topic < ActiveRecord::Base
has_many :linkers
has_many :articles, :through => :linkers, :foreign_key => :article_id
accepts_nested_attributes_for :articles
end
class Article < ActiveRecord::Base
has_many :linkers
has_many :topics, :through => :linkers, :foreign_key => :topic_id
end
class Linker < ActiveRecord::Base
#this is the join model, has extra attributes like "relevance"
belongs_to :topic
belongs_to :article
end
So when I build the article in the "new" action of the topics controller...
#topic.articles.build
...and make the nested form in topics/new.html.erb...
<% form_for(#topic) do |topic_form| %>
...fields...
<% topic_form.fields_for :articles do |article_form| %>
...fields...
...Rails automatically creates the linker, which is great.
Now for my question: My Linker model also has attributes that I want to be able to change via the "new topic" form. But the linker that Rails automatically creates has nil values for all its attributes except topic_id and article_id. How can I put fields for those other linker attributes into the "new topic" form so they don't come out nil?
Figured out the answer. The trick was:
#topic.linkers.build.build_article
That builds the linkers, then builds the article for each linker. So, in the models:
topic.rb needs accepts_nested_attributes_for :linkers
linker.rb needs accepts_nested_attributes_for :article
Then in the form:
<%= form_for(#topic) do |topic_form| %>
...fields...
<%= topic_form.fields_for :linkers do |linker_form| %>
...linker fields...
<%= linker_form.fields_for :article do |article_form| %>
...article fields...
When the form generated by Rails is submitted to the Rails controller#action, the params will have a structure similar to this (some made up attributes added):
params = {
"topic" => {
"name" => "Ruby on Rails' Nested Attributes",
"linkers_attributes" => {
"0" => {
"is_active" => false,
"article_attributes" => {
"title" => "Deeply Nested Attributes",
"description" => "How Ruby on Rails implements nested attributes."
}
}
}
}
}
Notice how linkers_attributes is actually a zero-indexed Hash with String keys, and not an Array? Well, this is because the form field keys that are sent to the server look like this:
topic[name]
topic[linkers_attributes][0][is_active]
topic[linkers_attributes][0][article_attributes][title]
Creating the record is now as simple as:
TopicController < ApplicationController
def create
#topic = Topic.create!(params[:topic])
end
end
A quick GOTCHA for when using has_one in your solution.
I will just copy paste the answer given by user KandadaBoggu in this thread.
The build method signature is different for has_one and has_many associations.
class User < ActiveRecord::Base
has_one :profile
has_many :messages
end
The build syntax for has_many association:
user.messages.build
The build syntax for has_one association:
user.build_profile # this will work
user.profile.build # this will throw error
Read the has_one association documentation for more details.