As I learned update_only doesn't work for has_many association.
I am updating nested attributes and it creates new children each time and don't delete old children. What would be the good way to delete them automatically on each update (to mimic :update_only behavior which works for has_one)?
I know about :allow_destroy. However, I don't have children id's to let rails know what should be destroyed.
P.S. I asked this question originally back in 2012 and back then I found a hacky solution to override assign_nested_attributes_for_collection_association. I wonder whether anything was introduced in Rails to solve this problem. It looks like very common straightforward case. I wonder why there is a built-in solution for it.
The best approach which I found is to override method assign_nested_attributes_for_collection_association on the object, check association type in there. if it's has_many then do destroy_all on this association.
Such code can be generalized and moved to a module for further reuse.
It looks like there is a better solution in town mentioned in these two questions:
Rails 4 has_many nested_attributes to replace all
Rails replace collection intead of adding to it from a has_many nested attributes form
The solution is
def foo_attributes=(*attrs)
self.foo.clear
super(*attrs)
end
Related
I'm looking to implement a site wide comment/message feature into my apps entities. This will enable people to comment on the following modules
Newsletters
Reports
Tasks
and user to user (messaging)
My plan would be to make a foreign key called "entity_id" which doesn't relate to any single table. Instead, it's coupled with commentEntity_id which is a list of all the tables that can be commented on.
Example:
So a comment would have a CommentEntity which points to Reports and also an entity_id which, in this case, is the id of the Reports table.
The way I would build this is to make the following tables
Comment #along with user_id and a comment body:string, this will also have a commentEntity_id and a entity_id
CommentInvolvement # simply everyone involved (either by commenting on the entity, or in the case of user to user, **being** the entity)
CommentEntity # This is the join between the comment and the place
it's put.
This would be my solution in a PHP project, though I understand Rails requires a different way of thinking, so I would like to get the community's thoughts on this problem, and wheather this is the best way to tackle it?
Thanks
Yes, Rails supports this approach through Polymorphic associations
comment.rb
belongs_to :commentable, polymorphic: true
other models
has_many :comments, as: :commentable
Note: You have to add two columns in comments table commentable_id(Integer) and commentable_type(String)
you should also check out the great RailsCast regarding Polymorphic Associations
d
Trying to find the definitive answer on whether active record associations should be in the list of attr_accessible attributes.
I've seen
class Foo
attr_accessible :name
attr_accessible :bars
belongs_to :bar
end
also seen
attr_accessible :bars_id
want to know the proper way to be able to do Foo.new(name: 'name' bar: barvar)
As often the definitive answer is: "It depends™"
Only the attributes you want to mass-assign should be made accessible.
So if you want or need to do…
Foo.new(name: 'name', bar: barvar)
…then you simply have to make bar accessible.
In the end assign_attributes is called which does a simple send("#{attribute_name}=", attribute_value) after checking the accessibility of the attribute.
Some coding style aspects:
Often mass assignment happens when processing the param hash. At least that's where the security problems are lurking. There you rarely have a Bar object but more often a bar_id.
However if you work with model instances, most people prefer using the association methods (as #Andrew Nesbitt wrote) because that often has some advantages (automatic saving, automatic update of the association counterpart, cleaner code, …)
So there are reasons to have one or the other or both.
My personal opinion: One should not waste a lot of time on this topic since Rails 4.0 will have a better solution for parameter sanitizing. (See strong_parameters if you want it in Rails 3, too)
You can avoid needing to make bar_id accessible by using the association builder:
# singular (has_one)
foo = bar.build_foo(name: 'name')
# plural (has_many)
foo = bar.foos.build(name: 'name')
The only time you would need to make an association accessible is if you are using accepts_nested_attributes.
While you can avoid making bars_id (shouldn't that be bar_id?) accessible in your example, the question is if parts of your application still needs access to it. Using active_admin, I had to make the whatever_id accessible to make things work with relations.
I'm using a Polymorphic relation with multiple tables. Object Window has ChartWindow, PluginWindow or PortletWindow. I used a class_eval (relate_to_details) technique to define detail tables so that each object can have it's own table with distinct attributes.
PluginWindowDetail is the detail table for PluginWindow. PluginWindow has a plugin_id (plugin_window_details.plugin_id) So, I defined a has_one association in PluginWindow ( has_one :plugin_window_detail, :dependent => :delete) because I want the Window to be deleted with the Plugin is deleted.
However, I realized that this isn't getting me what I want. Deleting the PluginWindowDetail won't delete the PluginWindow.. and since I'm using the class_eval technique instead of a regular ActiveRecord association, I'm not sure how I can do this without coding it myself (which maybe I should)
Anyways.. gists with code are here https://gist.github.com/3206666 . Any help would be appreciated.
I think the simpler way to do it is to use the before_destroy callback. It will be more flexible.
I'm trying to make a comment system, identical to the stackoverslow's one. There's a 1-st level comment, and a bunch of a second level comments, attached to it. The first straightforward idea that came up in order to do that, was to create a separate model, sub_comment. But I still got this feeling, that I should inherit the initial comment's properties, only adding a simple comment-id joint.
I'm still a Rails newbie, so the question is - can I do that? How? Is it a simple
class sub_comment < comment
model inheritance? But of course, I'm gonna need all the controller methods in order to add/delete these sub-comments. Or should I make a single comment model, where the 1-st level comment will have a nil as a parent?
What would be the right solution?
PS. Fell free to advice any book that covers the subject. I'm on a self education here, so I'd love to read everything that could clarify any future app architecture question.
I find it easier to have a simple tree structure with two pointers to make back-tracking easier:
class Post < ActiveRecord::Base
has_many :comments
end
class Comment < ActiveRecord::Base
acts_as_tree
belongs_to :post
named_scope :top_level, :conditions => { :parent_id => nil }
end
This makes it easy to retrieve all comments for a particular Post:
#comments = #post.comments.top_level
You can even pull up multiple levels of comments using AJAX or by fetching one layer after the next:
# Repeat while comments are found
loop do
level = #post.comments.find(:all, :conditions => { :parent_id => #comments.collect(&:id) })
if (level.empty?)
break
else
#comments += level
end
end
These can be sorted as required for presentation purposes.
One option is to have two separate classes, and put the shared behavior in a module. That way you're not doing Single Table Inheritance (which is what your inheritance example would mean, and it's something I have had problems with) but you also aren't duplicating code.
Conceptually, you're trying to create a self referencing association. This is certainly the cleanest way to go as you don't have extra tables and models to worry about, though it can be complex to setup.
I'd recommend checking out the Self Referential Association Railscast, it will walk you step by step how to get started.
The short and sweet answer is that I would go with your "parent id is nil" methodology for top-level comments and clearly sub comments would have parent id's. Remember, DRY rules the day in the Rails world. If you can use one class (in this case it makes sense), you should.
In Rails 2.2.2 (ruby 1.8.7-p72), I'd like to evaluate the impact of destroying an object before actually doing it. I.e. I would like to be able to generate a list of all objects that will be affected by :dependent => :destroy (via an object's associations). The real problem I'm trying to solve is to give a user a list of everything that will be deleted and having them confirm the action.
Can anyone recommend a good way to go about this? I've just started looking into ActiveRecord::Associations, but I haven't made much headway.
Update: In my particular case, I've got various levels of objects (A --> B --> C).
This should help get you started... Obviously you'll have to customize it but this lists all association names that are dependent destroy on the class BlogEntry:
BlogEntry.reflect_on_all_associations.map do |association|
if association.options[:dependent] == :destroy
# do something here...
association.name
end
end.compact
=> [:taggings, :comments]
Just manually maintain a list of associated object with dependent destroy (probably a go thing to do anyway) and then have named_scopes for each to pull in the included objects to display.
I'd say that as mentioned have a way of displaying affected records to the user, then have two buttons/links, one that is a delete, maybe with a confirm alert for the user which asks if they have checked the other link which is a list of all records they will be affecting.
Then if you want to be really sure you could also do a soft delete by marking them as deleted at in the database instead of actually deleting them which may well come in handy, I don't know how you would handle that on the automatic dependent delete, maybe with acts_as_paranoid, or some kind of self rolled version with a callback on the parent model.
Recently I wrote a simple Rails plugin that solves this problem.
Check it out on github: http://github.com/murbanski/affected_on_destroy/tree