Rails Counter Cache - ruby-on-rails

I have a db schema with multiple counter caches. Rather than have the counter caches spread across many tables, I'd like to have a table called user_counters with all of the various counter caches. I'd update these caches from a service object with a method like this:
def update_user_counter(column, user_id)
UserCounter.increment_counter(column, user_id)
end
But, the UserCounter record for the associated user will most likely not have the same ID as the user. What I'd like to do is something like this:
def update_user_counter(column, user_id)
UserCounter.increment_counter(column, UserCounter.where('user_id = "#{user_id}"')
end
Any thoughts as to how I can accomplish this? Thanks!!

If these are 10 attributes associated with a User, is there any reason to not simply make them a part of the User model? If that is the case, the below code will still work (just make the method part of User instead of UserCounter).
If you want to implement it your way, I'd say first make sure that your user_counters table has an index on user_id (since Rails is dumb about adding indexes and FK's).
Then, you could do this (passing in "column" as a symbol):
class UserCounter < ActiveRecord::Base
#other code
def self.update_user_counter(column, user_id)
counter = UserCounter.find_by_user_id(user_id)
counter.update_attribute(column, counter.read_attribute(column) + 1)
counter.save!
end
end
And in your other model:
class SomeOtherModel < ActiveRecord::Base
#Model code
def update_counter(user)
UserCounter.update_user_counter(:this_column, user.id)
end
end
This solution (or using the update_counter method) both require two database hits, one for the lookup and one for the update. If you need the speed, you could write this directly in SQL:
query = "UPDATE user_counters SET #{column} = #{column} + 1 WHERE user_id = #{user_id};"
ActiveRecord::Base.connection.execute(query);
This assumes that user_id is unique on user_counters.

Related

How to create a Rails 5 model that is actually a Database View created on the go?

I was reading this article: https://www.salsify.com/blog/engineering/eager-loading-calculations-database-views-in-rails
I really like the concept of creating a model that is actually a query to another model, in order to create some calculations.
What I'd like to do is currently very simple, for each Tag, I like to get the TagVisits and sum the visits attribute.
The code looks like this:
class TagVisitSummary < ApplicationRecord
belongs_to :tag
default_scope { set_from_clause }
def ==(other)
self.class == other.class && attributes == other.attributes
end
alias :eql? :==
def hash
attributes.hash
end
private
def self.set_from_clause
query = TagVisit.select(:tag_id).group(:tag_id).select('SUM(visits) as total_visits').all
from("(#{query.to_sql}) AS #{table_name}")
end
def self.columns
cols = [ActiveRecord::ConnectionAdapters::Column.new('tag_id', nil, :integer)]
cols << ActiveRecord::ConnectionAdapters::Column.new("total_visits", nil, :integer)
cols
end
end
When I paste the SQL generated in set_from_clause directly in my Postico, I see exactly the result I'm looking for. Unfortuntaly running TagVisitSumamry.all yields no results (and also no error). So, I'm having some doubts whether the self.columns method is correct. The example is in Rails 4, I'm using 5. Has there been some changes that require me to change this approach?
Thanks
Have you tried, scenic? https://github.com/thoughtbot/scenic
It helps with creating database views and materialized views and you can use them as ActiveRecord models.

Rails Models for multiple revisions of a type of document

I am trying to model this scenario with Rails
There are three types of ApplicationForms- FormA, FormB and FormC
This scenario is modeled using Single Table inheritance. And each form has a method tranform_to_pdf overwritten, which returns a map(header-->value).
application_forms(id, name, type)
Now it is possible to have multiple revisions for each form, for example, FormA has Rev1, Rev2, etc. Each revision might have modifications to the number of fields present etc.
What would be the best way to modify existing rails model to reflect revisions?
Thanks in Advance!
I would delegate the work to a second set of objects. Assuming you have a forms table with 3 fields (id, the polymorphic type and the revision (version) ). I would write something like:
class Form < ActiveRecord::Base
end
class FormA < Form
def transform_to_pdf
end
private
REVISION_TO_IMPLEMENTATION_MAP = {
1 => FormAImplementationV1,
2 => FormAImplementationV2
}
def implementation
klass = REVISION_TO_IMPLEMENTATION_MAP[revision]
klass.new(self)
end
end
class FormAImplementation
def initialize(model)
end
end
class FormAImplementationV1 < FormAImplementation
def transform_to_pdf
end
end
class FormAImplementationV2 < FormAImplementation
def transform_to_pdf
end
end
However I think this could quickly become unwieldy and it would quickly make sense to implement another solution, either with meta-data or by storing revision and field in your database. I would need to see more code though...

Rails4 : How to assign a nested resource id to another resource

Model:
order & material
order has_many materials
material belongs_to order
material & user
material has_may users
user belongs_to material
Assume I create a material with id = 20 , order_id = 1
In materials_controller update action, I want to assign material id to specific users.In materials_controller update action I did it like this
if #material.update_attributes(material_params)
if #material.ready == true
#users = User.where("is_manager = 't'")
#users.each do |user|
user.material_id = #material.id
end
end
end
But attribute material_id in user did not get changed after the action.
Anybody could tell me what cause the failure to pass material id to user ?
You also need to do user.save after you change user.material_id.
user.material_id = #material.id
user.save #Saves the changes
That changed the attribute of user object, but the change has not been persisted yet. It's now a stale object which has some attributes changed. To persist those attributes, user.save is required.
Or you can use update_attribute like below:
if #material.update_attributes(material_params)
if #material.ready
#users = User.where("is_manager = 't'")
#users.each do |user|
user.update_attribute(:material_id, #material.id)
end
end
end
You might want to have a look at update_all.
update_all updates all records in one SQL statement instead of loading all records into memory and sending N update queries to the database. This makes update_all much faster than iterating over multiple users.
if #material.ready
User.where(is_manager: 't').update_all(material_id: #material.id)
end
Often it is an issue, that update_all doesn't validate the records before updating. But in this case this behavior is actually preferred.

Get data through association

Tables:
User
Project has_many Results
Project has_many Data through ProjectData
Results belongs_to data, project
In the Result table I have a column :position of type int.
So I would like to get all the results with a level < 50, actually the value of count.
I am thinking in adding in the Result class
def get_top_level current_user
tsum = []
Project.where(user_id: current_user).each do |project|
tsum << project.results.where("level <= ?", 50).count
end
return sum(tsum)
end
This will work, but I feel that there should be a easy and prettier way of doing this.
And is it ok to user the class name in a view and pass different values for example:
<%=Results.get_top_level(current_user)%>
Or
<%=#results.get_top_level(current_user)%>
If none of those are a good practice, can you help me with a alternative solution for this.
Thank you.
I would create a method on the project model something like this.
def get_top_level
self.results.select{ |result| result.level <= 50 }
end
On the user model. What's the relationship here, does a user have many projects? Or just one project.
def get_top_level
self.top_level_projects.inject(:+)
end
def top_level_projects
self.projects.map(&:get_top_level)
end
Now when you call current_user.get_top_level
This will find the top_level_projects, map the associated results and add them all together.

How to tell what class is calling a method in Ruby on Rails?

In my Rails app I have this:
class Invoice < ActiveRecord::Base
has_many :payments
before_save :save_outstanding_amount
def save_outstanding_amount # atomic saving
self.outstanding_amount = new_outstanding_amount
end
def update_outstanding_amount # adds another SQL query
update_column(:outstanding_amount, new_outstanding_amount)
end
private
def new_outstanding_amount
total - payments.sum(&:amount)
end
end
How can make this dynamic, so that the first method gets called from all instances of the Invoice class and the second method gets called from all instances of other classes, e.g. the Payment class?
You are doing something very dangerous here. Make sure use sql to change the value instead of setting a new value.
Imagine the following:
Invoice amount: 1000
User1 deducts 100, but does it with a very slow request.
Right after user 1 starts, User2 deducts 500, and does it really quickly.
Since you're doing this stuff within the application, you end up with an outstanding amount of 900, since that's the last executed method. update_counters creates a 'safe' sql query.
self.class.update_counters self.id, payments.sum(&:amount)
This also solves your question on how to call them from two classes. Always update those columns like this.

Resources