Increment views in Rails call also the model callbacks - ruby-on-rails

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)

Related

Rails - mean calculation of parent value based on children

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

How to avoid a circular loop

I think I'm being dense here because I keep getting a stack too deep error...
I have a Child and a Parent relational objects. I want 2 things to happen:
if you try to update the Child, you cannot update its status_id to 1 unless it has a Parent association
if you create a Parent and then attach it to the Child, then the Child's status should be auto-set to 1.
Here's how the Parent association gets added:
parent = Parent.new
if parent.save
child.update_attributes(parent_id:1)
end
I have these callbacks on the Child model:
validate :mark_complete
after_update :set_complete
# this callback is here because there is a way to update the Child model attributes
def mark_complete
if self.status_id == 1 && self.parent.blank?
errors[:base] << ""
end
end
def set_complete
if self.logistic.present?
self.update_attribute(:status_id, 1)
end
end
The code above is actually not that efficient because it's 2 db hits when ideally it would be 1, done all at once. But I find it too brain draining to figure out why... I'm not sure why it's not even working, and therefore can't even begin to think about making this a singular db transaction.
EXAMPLE
Hopefully this helps clarify. Imagine a Charge model and an Item model. Each Item has a Charge. The Item also has an attribute paid. Two things:
If you update the Item, you cannot update the paid to true until the Item has been associated with a Charge object
If you link a Charge object to a Item by updating the charge_id attribute on the Item, then code should save you time and auto set the paid as true
There's a lot that I find confusing here, but it seems to me that you call :set_complete after_update and within set_complete you are updating attributes, thus you seem to have a perpetual loop there. There might be other loops that I can't see but that one stands out to me.
One way to avoid a circularly recursive situation like this is to provide a flag as a parameter (or otherwise) that will stop the loop from continuing.
In this case, (though I am not sure about the case entirely) I think you could provide a flag indicating the origin of the call. If the origin of the update is a charge being attached, then pass a flag that will stop the check from happening or modify it to keep the loop from happening. Perhaps a secondary set of logic is in order for such a case?
I faced a stack level too deep problem some time back when working with ActiveRecord callbacks.
In my case the problem was with update_attribute after the update goes through the callback i.e. set_complete in your case is called again in which the update_attribute is triggered again in turn and this repeats endlessly.
I got around that by using update_column instead which does not trigger any callbacks or validations however setting a flag is what was advised more often online.
At this point I do not have an answer for reducing your database write operations, and will add to this answer if I can think of anything.
Hope this helps

keeping 2 model records in sync - callbacks infinite loop

I have 2 records of the same model, and I want to keep some of the data on these records in sync.
I was going to do a after_save callback (or maybe observer) to trigger updating the other record, but I am afraid this is going to cause an infinite loop of saves because the other record will cause a callback.
I read here that you can bypass callbacks on save, but these approaches seem to be hackish and not consistent between rails 2 and 3 (we are moving to rails 3 in a couple months).
Is there a better option?
You can create attr_accessor:
attr_accessor :dont_run_callback
after_save :my_callback
def my_callback
MyModel.find(1).update_attributes(..., :dont_run_callback => true) unless dont_run_callback
end
something like that
You can use the update_columns method while updating the 2nd record based on updates on the first one and vice versa.

Which callback does ActiveRecord use to record timestamp?

I'm just wondering here whether any of you guys know when ActiveRecord use it's "magic" to record the timestamp (e.g. created_at, updated_at).
What i mean when is, at which callback ? (if AR use callback at all).
I'm asking this because I want to create an auto-updating column (that record sequential number for each object) and I want to replicate AR way to do this as much as possible.
EDITED:
It seems that AR does it between after_validation and before_create/before_update. You can do some tests for this by creating a presence validation for created_at column and inserting new record with blank created_at, it would return an error.
I don't know where AR does it, but the proper place for what you describe sounds like before_create
In Rails 3.2.12, this code is located in lib/active_record/timestamp.rb.
As you mention in your question and DGM suggests, Rails will update the timestamps when creating or updating, so sticking your code in before_create and before_update should work.
You may also want to take a look at the ActiveRecord counter_cache functionality. ActiveRecord supports creation of a column that can automatically be incremented/decremented. Additionally, you can perform more complicated logic.

Triggers/Callbacks in Ruby on Rails

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.

Resources