I've been wondering, suppose I have a model with an attribute that in every instance is dependent on that same attribute in other instances. The best example for this would be an order attribute for items in a list.
The best place to update the rest of the items' order attributes would be in a before_update callback method, were you have both the item's old and new values.
But now whenever you update the other items in the list the callback is going to be called again, and again...
I'm looking for an elegant way of solving this.
I have heard about the :update_without_callbacks method, but i don't want to use a private method, and also i feel like adding extra attributes would be unnecessary.
Got any good ideas? Thanks in advance!
One way would be to use update_all to set the order of all the other items in bulk.
That way you would efficiently limit the number of queries to one and prevent any callbacks from being triggered.
https://github.com/rails/rails/blob/83e42d52e37a33682fcac856330fd5d06e5a529c/activerecord/lib/active_record/relation.rb#L274
I feel the fact that you have to do this type of update across entries suggests you haven't properly conceptualized your problem. Why not create a List model that has the order attribute, and then create a one-to-many relationship between the List model and the Item model. This way, there's only one place to update the ordering information and no need for complicated and brittle callbacks.
Related
I have a model and can manually check every (:has_many, :has_one) dependency, but I want some magic like current_user.attributes for records. So when I update model, I don't need to update method.
I tried Reflections, but it returns all theoretical dependencies/connections of model, isn't it? And I need dependent records from DB.
Something like X.where(user_id: #user.id) or #user.dependents
Is it possible?
You can assign required object to model and then
model.class.reflect_on_all_associations.map { |table| model.method(table.name).call }.select(&:any?)
For example:
user = User.first
user.class.reflect_on_all_associations.map { |table| user.method(table.name).call }.select(&:any?)
# returns all associated objects of first user
You can specify result using :has_many, :has_one or :belongs_to as argument of reflect_on_all_associations.
Possibly there is more elegant way, but it works.
TL;DR Don't do this :)
You can do something quite similar using reflections. For example
#user.class.reflections.keys.flat_map { |reflection| me.send(reflection) }
will give you an array with all the objects associated with the user. But what's next?
For almost any real-world logic around this list's members (except the basics that come from AR::Base) you will have to check either a class of an object or use bug-prone try magic - both options are reasonable trade-off sometimes, but in most practical cases I'd prefer less smelly solutions (even if they are a bit more verbose).
I have a Invoice model which accepts_nested_attributes_for :line_items with allow_destroy: true. In my model, in a before_save callback, I can reference the data like this:
self.line_items
Some of the items will be deleted upon save. I want to be able to grab only the items that won't be deleted like this:
self.line_items.where(_destroy: false)
However, this obviously won't work since I am dealing with an unsaved object. So, my question is how do I get the list of items that won't be deleted? I know I could technically iterate through the list and add each applicable item to a new array, but I figure there is something more intuitive. For example, currently I use .sort_by(&:line_number) rather than the SQL .sort(:line_number) for sorting which allows me to sort in memory rather than from SQL. I need the same thing except for a .where clause.
Thanks in advance.
As far as I'm aware, where is aimed specifically at building SQL queries, so you can't use it to deal with in-memory criteria like whether something will be destroyed on save. For that, you can use the methods in the Enumerable module, which is included in the collection object. The sort_by call you mentioned is using that module. In this case, you'd probably use line_items.reject(&:marked_for_destruction?). See the documentation for marked_for_destruction? for more details.
Is it possible to write an ActiveRecord query that sorts by an association field if the association exists, and otherwise sorts by an attribute on the object itself?
Example: I have a Discussion object which has_many :comments. I'd like to display a list of discussions sorted by discussion.latest_comment.created_at. However, some discussions may not have any comments, in which case I would like use their discussion.created_at attribute instead.
The catch is that I need the result to be an ActiveRecord::Relation (for performance reasons, and also because we are using Kaminari [which requires a Relation object]).
The only thing I could think of is to actually create a new field like discussion.latest_comment_at which would be initially populated by discussion.created_at and then updated every time a new comment is posted. However, this doesn't seem very straight-forward from a maintenance perspective (e.g. what happens when a comment gets deleted?).
I don't know of a way to do this through SQL, so I cheated and I have my code set a last_post_at attribute whenever a topic is created.
That way, I can then sort the topics by last_post_at rather than having to query two tables at once.
I've seen other forum systems do it this way too, and it seems like what you're designing is exactly a forum-like system.
What is the most efficient way to delete an existing object from a Rails class? I have an array of objects
person_array = [Person1, Person2, Person3]
My goal is to delete all of the people in that array from a table in which they are members.
So I have a People class which they currently belong to.
I was thinking something like People.delete(person_array) which seems to be executing the proper SQL statements. How do I get those delete statements to actually change my People table though?
Since you already have the objects, you can just call destroy_all with them:
Person.destroy_all(:id => person_array)
Unlike delete_all, this will invoke the model's callbacks, which may take longer, but preserves integrity.
Not sure if you need to get the ids or Activerecord does that but for you.
Person.delete_all person_array.map(&:id)
When working with a nested model in a form, is there a way to sort the nested models in the view or in the controller? The closest answer I could find was here, but both of those solutions involve putting sort orders in the model. I'd rather avoid changing things fundamentally like that and keep this particular sort order to just one page.
You can always sort with ruby: parent.children.sort{|a,b| a.field <=> b.field} or something like that
Or you can add a find method to parent model, like def self.find_ordered_by_field
Can't think of another options...
Putting the sort in the models is absolutely the way to go. You shouldn't have any more ruby code than necessary in your views themselves, because it's much harder to test that your sorting is working the way you think it should.
When you add the sort at the model level (and either answer in the link you posted works well) you can add an automated test to verify that it is, in fact, sorting the way you'd like. This is business logic, and it belongs in the model.
Sometimes, however, you're looking to sort in a way that is NOT what you would normally want for this model. Maybe you normally want them sorted by name, but in this one view you want them sorted newest to oldest. At the very least, do the sort in the controller so once again it can be fully tested. But I would still put it in the model, personally.