Ruby on Rails Increment Counter in Model - ruby-on-rails

I'm attempting to increment a counter in my User table from another model.
class Count < ActiveRecord::Base
belongs_to :user
after_create :update_count
def update_count
user = User.find(self.user_id)
user.increment(:count)
end
end
So when count is created the goal would be to increment a counter column for that user. Currently it refuses to get the user after creation and I get a nil error.
I'm using devise for my Users
Is this the right (best practice) place to do it? I had it working in the controllers, but wanted to clean it up.
I'm very inexperienced with Model callbacks.

If User has many Counts and Count belongs to User (like it seems to be), then you might want to use a counter cache. It does exactly what you want to do, and it is built-in into ActiveRecord.

I think a better place for this would be using an observer that listens for the on_create for User objects, and then runs this logic.
Something like:
class UserObserver < ActiveRecord::Observer
def after_create(user)
Counter.find_by_name("user_count").increment
end
end

If you would like more extensible counter caches, check out counter_culture. It supports basic counter cache functionality, but also allows you to create counters of records that meet various conditions. For example, you could easily create an inactive user count with code like this:
class Product < ActiveRecord::Base
belongs_to :category
counter_culture :category, :column_name => \
Proc.new {|model| model.inactive? ? 'inactive_count' : nil }
end

Related

counter_cache in Rails on a scoped association

I have User model which has_many :notifications. Notification has a boolean column seen and a scope called :unseen which returns all notifications where seen is false.
class User < ApplicationRecord
has_many :notifications
has_many :unseen_notifications, -> { unseen }, class_name: "Notification"
end
I know that I can cache the number of notifications if I add a column called notifications_count to users and add counter_cache: true to my belongs_to call in Notification.
But what if I want to cache the number of unseen notifications a user has? I.e. cache unseen_notifications.size instead of notifications.size? Is there a built-in way to do this with counter_cache or do I have to roll my own solution?
According to this blog post, there is no built-in way to use counter caches for scoped associations:
ActiveRecord’s counter cache callbacks only fire when creating or destroying records, so adding a counter cache on a scoped association won’t work. For advanced cases, like only counting the number of published responses, check out the counter_culture gem.
Other than using an external gem, you could roll your own solution by adding callbacks to your Notification model and adding an integer column (e.g., unseen_notifications_count) to the User model:
# Notification.rb
after_save :update_counter_cache
after_destroy :update_counter_cache
def update_counter_cache
self.user.unseen_notifications_count = self.user.unseen_notifications.size
self.user.save
end
Also see the answers to Counter Cache for a column with conditions? for additional implementation examples of this approach.

Model column that depends on other columns

I have a gamification app that has four types of points, and the sum of all these kinds is the total points for a user, I want to be able to do sum and scopes on that column, so I think I should have it as a column in the DB.
scope :points_rank, -> { order(points: :desc) }
I was using a before_save for adding all four point types and storing it in points, but now I'm using a gem that does increment to these types of points, so when it updates those values, the before_save is not called, hence not updating the points value as expected.
What is the correct ActiveRecord callback to be using instead of before_save, or what else could I be doing to keep the column updated.
Try using the after_touch callback instead.
after_touch callback is triggered whenever an object is touched.
So, whenever point type changes, it should update the points.
First of all, counter_culture seems to be a way to enhance the counter_cache functionality of rails...
Used to cache the number of belonging objects on associations. For example, a comments_count column in a Post class that has many instances of Comment will cache the number of existent comments for each post.
It might not be exactly what you want, judging from your question.
Okay I get it. You're using points in your User model to create a "cached" column which can be used for wider application functionality. Okay that's cool...
--
Your setup, then, will look something like this (you were manually setting the counter_cache column, and now the gem handles it):
#app/models/user.rb
class User < ActiveRecord::Base
counter_cache :points
end
#app/models/point.rb
class Point < ActiveRecord::Base
belongs_to :user, counter_cache: true
end
The question is then that when you update the points model, you need to be able to update the "cached" column in the users model, now without any callbacks.
What is the correct ActiveRecord callback to be using instead of before_save
I'm presuming you're calling before_save on your User model (IE adding the associated data and putting the points column?
If so, you should try using a callback on the Point model, perhaps something like this:
#app/models/user.rb
class User < ActiveRecord::Base
has_many :points
end
#app/models/point.rb
class Point < ActiveRecord::Base
belongs_to :user, inverse_of: :points
after_commit :update_user
private
def update_user
if user?
user.update(points: x + y + z)
end
end
end
--
Oberservers
If you have real problems, you could look at ActiveRecord observers.
Here's an answer I wrote about it: Ruby On Rails Updating Heroku Dynamic Routes
Whether this will trigger without any callbacks is another matter, but what I can say is that it will work to give you functionality you may not have had access to otherwise:
#config/application.rb (can be placed into dev or prod files if required)
config.active_record.observers = :point_observer
#app/models/point_observer.rb
class PointObserver < ActiveRecord::Observer
def before_save(point)
#logic here
end
end
A good way to test this would be to use it (you'll have to use the rails-observers gem) with different methods. IE:
#app/models/point_observer.rb
class PointObserver < ActiveRecord::Observer
def initialize(point)
#if this fires, happy days
end
end

Counter Cache on a condition not on association

I've read on counter caches on associations.
Is there a way to easily implement (via a gem that will do the heavy lifting) of a counter cache for some condition? for example:
usually a counter cache would be
class User
has_many :messages
class Message
belongs_to :user, counter_cache: true
however, lets say that I don't want to count how many messages, but to count the total number of characters in all of the messages from Joe
so lets say I have a method count_chars_from(user) that returns the number of chars from a user
I want to update a specific column when many thing occur in the system (when joe sends a message to several people - all of those users need to be updated, when joe edits a message to one person, etc)
This could be done with observers I guess, but I see myself creating ugly code very quickly.
Is there a way to have something like the above?
Currently, there is no special methods/helpers for this. Rails doesn't support conditional counters.
However, you can simply define your own.
class Message
belongs_to :user
after_create :inc_counters
after_destroy :dec_counters
private
def inc_counters
if user && your_conditions
user.increment(:conditional_counter)
end
end
def dec_counters
if user && your_conditions
user.decrement(:conditional_counter)
end
end
end
I'd suggest take a look at counter_cache implementation. It looks pretty complex, but simple implementation works in one of my project pretty well.
Also consider to use update_column to not trigger validations and callbacks on User model.
And test you code when you delete parent model if it has dependent: :destroy on association.

How to make default_scope in Rails model dynamic?

In one of my Rails models I have this:
class Project < ActiveRecord::Base
belongs_to :user
default_scope order("number ASC")
end
Now the problem is that I want each user to be able to set his or her default_scope individually. For example, a user A might want default_scope order("date ASC"), another one might want default_scope order("number DESC").
In my User table I even have columns to store these values: order_column and order_direction.
But how can I make the default_scope in the model dynamic?
Thanks for any help.
As #screenmutt said, default scopes are not meant to be data-driven, they are meant to be model driven. Since this scope is going to change according to each user's data I'd use a regular scope for this.
#fmendez answer is pretty good but it uses default scope which I just explained why it is not recommended using this method.
This is what I'd do in your case:
class Post < ActiveRecord::Base
scope :user_order, lambda { order("#{current_user.order_column} #{current_user.order_direction}")}
end
Also a very important thing to notice here is SQL injection: Since you are embedding current_user.order_column and current_user.order_direction inside your query, you MUST ensure that the user can only feed these columns into the database with valid data. Otherwise, users will be able to craft unwanted SQL queries.
You won't want to use default_scope. What you do what is regular scope.
class Post < ActiveRecord::Base
scope :created_before, ->(time) { where("created_at < ?", time) }
end
Scope | Ruby on Rails
You could do something like this:
def self.default_scope
order("#{current_user.order_column} #{current_user.order_direction}")
end
This should dynamically pick the values stored in the current_user's order_column and order_direction columns.
You can define a class method with whatever logic you require and set your default scope to that. A class method is identical to a named scope when it returns a relation,eg by returning the result of a method like order.
For example:
def self.user_ordering
# user ording logic here
end
default_scope :user_ordering
You may want to add a current_user and current_user= class methods to your User model which maintains the request user in a thread local variable. You would typically set the current user on your User model from your application controller. This makes current_user available to all your models for logic such as your sorting order and does it in a thread safe manner.

Elegant alternative to after_create callbacks in Rails?

I have two models
class Contract < ActiveRecord::Base
has_many :transactions
end
class Transaction < ActiveRecord::Base
belongs_to :contract
after_create :mark_contract_as_live
def mark_contract_as_live
k = self.contract
if !k.is_live
k.update_attributes(:is_live => true)
end
end
end
is_live is a boolean field in the Contract model. A contract is defaulted to not live (is_live => false) when it created. When the first transaction is recorded it is marked as live (is_live => true). With the solution, I have above, it means that every transaction creation requires calling the database to check whether contract is live. Is there an alternative to this?
If contract has thousands of transactions, that means this will be called thousands of times although it is only relevant to the very first transaction.
In a general sense, what is an elegant way to implement callbacks. This seems messy?
class Contract < ActiveRecord::Base
has_many :transactions
def mark_as_live
update(is_live: true) unless is_live?
end
end
class Transaction < ActiveRecord::Base
belongs_to :contract
after_create :mark_contract_as_live
private
def mark_contract_as_live
contract.mark_as_live
end
end
It is the Contract class responsibility to care if a contract should be marked as live or not. The Transaction class should not handle this. So I created a mark_as_live in the Contract class and call it in the Transaction after_create callback.
I would prefer to use a guard clause in the mark_as_live method like so:
def mark_as_live
return if is_live?
update(is_live: true)
end
But because it is a very short method, it probably does not worth it.
Note also that ActiveRecord adds methods like xxx? for boolean field. A question mark at the end of the method conveys more clearly what you want to say.
Finally, but this is a question of taste, I dislike prefixing my boolean attributes with is_xxx. I don't use RSpec and may be wrong but I think that it adds some predicate matchers like be_xxx for the xxx attribute and it may go weird with the is_xxx attributes. Because a lot of people are using RSpec, it may become a bit of a convention.
If contract has thousands of transactions, that means this will be called thousands of times although it is only relevant to the very first transaction.
The Contract instance will be still loaded if you create a transaction like so: contract.transactions.create(transaction_params). So the call to is_live? will come at no cost, you don't have to worry.

Resources