Book model has_many Ratings, which has overall attribute.
I want the Book's mean_rating attribute to be updated every time a rating is added/destroyed.
What is the easiest way to do it?
you can utilize the model lifecycle hooks, notable, after_create.
in your ratings model, you can write an after create hook that will update the mean_rating
#models/rating.rb
after_create :update_book_mean_rating
def update_book_mean_rating
new_mean_rating = self.book.ratings.sum(:overall) / self.book.ratings.count
self.book.update_attributes(mean_rating: new_mean_rating
end
you'll probably want to add validations / checks that makes sure a rating always has a book etc and doing something similar when destroying, but something like this should point you in the right direction
The easiest way (not thread-safe) is to use after_create/after_destroy callbacks in Rating model. See documentation on model callbacks
If you need thread safe solution you will need to update mean_rating in delayed job or something. This post might be a good start
Related
I have a dumb question.
To record views in my content I'm incrementing a value in my db with something like this:
#gallery.increment! :impressions_count
I have a lot of before_save callbacks in my model, and for every view I call also the callback. I don't want this, and I know that I can also call a skip_callback .
Is there a smarter way to keep track of impressions avoiding this problem?
You could do this:
Gallery.increment_counter(:impressions_count, #gallery.id)
I might also choose this way, update column is the standard way to avoid callbacks and updated_at changes.
#gallery.update_column(:impressions_count, impressions_count + 1)
In Rails's ActiveRecord we can do things such as before_filter or before_create.
I have a case that when a certain class of data is retrieved, thus from certain model, the accessor need to be recorded. How to implement this in Rails? What filter to use? Even if there is no filter, can I know certain method that can be used for this kind of task?
Updated:
For example. There is a model called "Document" that this model need a high security. Nevertheless, when any user access record from this model, he need to be recorded. In such case, can I use some filter so that before a record is retrieved, I am able to record the user in question. Even if there is no any filter for that, may I know any other techniques that I can employ?
Sorry I am not an English speaker. :)
You actually need to have some extra logic to an existing model.
What you can do is extract that extra logic to a service object which would wrap your original model with the desired behavior.
eg:
#wrapped_ticket = TicketWrapper(a_ticket)
and
class WrappedTicket
def initialize(ticket)
#wrapped_ticket
..
end
def get_ticket
record_access
return #wrapped_ticket
end
private
def record_access
...
end
end
you only use TicketWrapper objects and collections and TicketWrapper has all the desired functionality
I suggest you read this article for more info.
Ohellair (read: Hallo in POSH accent).
Well. Actually ActiveRecord have the proper callback, haven't yet tried but have a look at http://guides.rubyonrails.org/active_record_callbacks.html after_initialize and after_find.
I am sorry. I think I should really try to read documentation before asking. (You know, that some documentation not really that up-to-date right? I get used to that. And I new to Rails. So sorry for 'duplicating' if it is).
Thanks.
Unfortunately I'm probably still too much a Rails beginner, so, even though I thought about and tried different approaches, I didn't get to work what I want and now have to ask for help again.
I have a REST comment vote mechanism with thumbs up and down for each comment. That works fine, each handled with counter_cache to count. Now, based on these thumbs up and down votes, I want to calculate a plusminus value for each comment, thumbs_up-votes - thumbs_down-votes. Although I'm not sure if it's the most efficient way to deal with that, I am planning to have the plus-minus value as an extra integer attribute of the comment model (whereas the thumbs up and down are own models). So, what I basically want is, that when a thumbs_up is saved, the comment's plusminus attr automatically should be += 1, and respectively for the thumbs_down.save a -= 1.
How can I issue such an action from within the thumbs_up controller? Do I need to modify my form_for or is my approach completely wrong?
Is there an after_save callback to deal with an attribute of a different model?
From what you've given, it's hard to tell. But I'd say that if you need to show a comment's "thumbs up" and "thumbs down" independently, store them as fields for your Comment model. Then, just making a helper method in your Comment model to get a comment's rating:
def rating
thumbs_up - thumbs_down
end
Edit:
With your new comment, I'd still say make a helper method rather than a field.
#models/comment.rb
def rating
thumbs_up.all.length - thumbs_down.all.length #or whatever way you want to do this
end
if you don't want to mix two different models with helper methods that don't actually belong to neither of those models, you can use Observers http://api.rubyonrails.org/classes/ActiveRecord/Observer.html
your observer will watch one model and do something
I was wondering, let's say I have two models, discussions and post. Posts belong to discussions. I have a custom action in a Discussion model that acts to check the latest activity on an object by checking the creation date of its newest post. ie
def latest activity
self.posts.last.created_at || self.created_at
end
Can i write a scope that orders the model's objects by this method rather than, say, a column in the Discussion model?
scope :latest_activity, #what to put here?
is there a reason why you don't want to alter the parent? Rails have a built-in method called touch for this exact thing. For example: belongs_to :discussion, :touch => :last_reply_at will make it so whenever a reply is changed(created/updated) the parent will have that field updated with Time.now timestamp. One benefit of this would be the ability to not query the children every time you want to see which discussion has the last reply.
Have a look at http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
EDIT
Another way to do this would be to add a filter, for instance after_create on the post update a field with the current time on the discussion model.
We are creating a system in Ruby on Rails and we want to be able to offer our users a bit of control about notifications and actions that can take place when some pre-defined trigger occurs. In addition, we plan on iterating through imported data and allowing our users to configure some actions and triggers based on that data.
Let me give you a few examples to better clarify:
Trigger - Action
------------------------------------------------------------------------
New Ticket is Created - User receives an e-mail
New Ticket Parsed for Keyword 'evil' - Ticket gets auto-assigned to a
particular group
User Missed 3 Meetings - A ticket is automatically created
Ideally, we would like some of the triggers to be configurable. For instance, the last example would possibly let you configure how many meetings were missed before the action took place.
I was wondering what patterns might help me in doing this event/callback situation in Ruby on Rails. Also, the triggers and actions may be configurable, but they will be predefined; so, should they be hard coded or stored in the database?
Any thoughts would be greatly appreciated. Thanks!
Update 1: After looking at it, I noticed that the badges system on SO is somewhat similar, based on these criteria, I want to do this action. It's slightly different, but I want to be able to easily add new criteria and actions and present them to the users. Any thoughts relating to this?
I think that what you are looking for are the Observers.
In your examples the Observers could handle the first and the third example (but not the second one, since an Observer only observes the object, not interact with it, even though it is technically possible).
Some code to show how I mean:
class TicketObserver < ActiveRecord::Observer
def after_create(ticket)
UserMailer.deliver_new_ticket_notification
end
end
class UserObserver < ActiveRecord::Observer
def after_update(user)
Ticket.new if user.recently_missed_a_meeting and user.missed_meetings > 3
end
end
And then add the observers to environment.rb
config.active_record.observers = :user_observer, :ticket_observer
Of course you will have to fill in the logic for the missed_meetings, but one detail to mention.
Since the after_update will trigger after every time that the user is being updated, the recently_missed_a_meeting attribute is useful. I usually follow the thinking of restful-authentication and have an instance variable that is being set to true everytime I want to trigger that row. That can be done in a callback or in some custom logic depends on how you track the meetings.
And for the second example, I would put it in a before_update callback, perhaps having the keywords in a lookup table to let users update which words that should trigger the move to a specific group.
You should look at the "callback" methods in Rails
For docs see - Callbacks
Your first rule would be implemented via the after_create method.
If you want them to be configurable, I would suggest using a model / table to store the possible actions and doing a lookup within the callback.
If this is high volume, be sure to consider caching the configuration since it would end up doing a db lookup on each callback.
Maybe something like a state-machine can help. Try AASM gem for RoR.