Let's say I have two models: User and Point.
Point belongs_to the user. Point has an 'amount'. I want to define a method in the Point model that will return the user's total points. I'd like to call it in such a manner: user.points.total
I just don't know how to define the method in the Point model in such a way that I don't have to pass it the User's ID. I'd imagine there is a simple way to do this, but my Googlefu is failing me.
Assuming that User model has
has_many :points
you can do like this in User model
def total_points
points.sum(:amount)
end
Given that you want the method defined in the Point model, you'd want to use:
class Point
def total
user.points.sum(:amount)
end
end
You can then get the total points for the user associated with a given point by invoking point.total. You can't get total points through User.points.total because User is a class and doesn't refer to a particular user.
Consider using #sum, which uses SQL sum to do an aggregation.
For example,
person.points.sum('amount')
or
# inside User.rb
def total
points.sum('amount')
end
Related
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
In my program, I have a model, Calorie, that takes what a person ate and gives them a point total. After that point value is calculated for each day's nutritional information, I want to update the 'points' variable in the User model.
The code I have in the Calorie model is
before_save :calculate_points
def calculate_points
# snipped calculations
User.where(user_id).first.point_calculation
end
In the User model, I have
def point_calculation
self.points = Calorie.where(user_id: id).sum(:points)
end
I've tested the point_calculation model by creating a callback before_save, and it works fine there. But it makes a lot more sense to update after each new Calorie entry, instead of a user updating their settings. Any advice? What am I missing?
Thanks for your help.
I'm assuming your Calorie model has a has_one relationship with the User, and User has_many Calories.
In Calorie model:
after_save :update_user_points
def update_user_points
self.user.update_calorie_points!
end
In User model:
def update_calorie_points!
self.update_column(:points, self.calories.sum(:points))
end
Suppose in my rails application I have a model Entry, which has a nested model Measures, such that each entry has_many measures (and measures belongs_to entry).
Each measure has its own incentive. Is it possible that Entry has an integer also named incentive, whose value is equal to the sum of all of its measures? How do you achieve this?
To me, it seems like this kind of becomes a two part question:
How to make a models field, upon submission, be defined based on another fields value? Then.. How to make a value, upon submission, be defined based on its nested models values?
Try implement a callback using after_update in the model of the nested attributes, which updates its parent:
class Measure < ActiveRecord::Base
after_update :calculate_measure_sum
...
private
def calculate_measure_sum
# calculate sum
self.entry.save
end
end
You might need to use the same method on the after_create callback as well.
EDIT:
After having read about touch in another question, I'd like to update my approach:
class Entry < ActiveRecord::Base
has_many :measures
after_touch :calculate_measure_sum
...
private
def calculate_measure_sum
# calculate sum
self.entry.save
end
end
class Measure < ActiveRecord::Base
belongs_to :entry, touch: true
...
end
What happens here, is that everytime a Measure is created or edited, it informs its Entry that it is updated by calling its touch method. In the entry, we may use the callback after_touch in order to recalculate the sum of the measures. Note that the after_touch-callback is called on creation, deletion and modification of the measures.
Compared to my previous approach, this approach puts the responsability on the Entry-objects, which is favourable from a design point-of-view.
Working on a Rails 3.1 project using Devise.
My User model has_many :courses and a Course belongs_to :user.
The User has a boolean attribute miles that the Course model needs to check before saving. I'm trying to use the before_save callback in the Course model like this:
# check if user preference is miles, if so convert entered value to kilometers. All
# distances are stored as kilometers and converted to miles if necessary
before_save :convert_distance
def convert_distance
if course.user.miles
course.distance = course.distance * 1.6
end
end
Obviously I can't use course.user.miles because the new course hasn't had the user_id set yet.
So how can I check the value of the user.miles attribute? Is this something I need to do in the controller?
Actually, it depends on how you're creating the Course for the User. For example, this works in a CoursesController that also knows who the user is:
course = user.courses.build(params[:course])
if course.save
# ...
end
Using #build on an association assigns the appropriate parent id, so the user will be visible from the course.
You could use a after_commit callback in this case.
Hey I am stuck with my orientation in rails.
I got a User model, a Course Model and a CourseEnrollment Model.
When I want to add a link in my Course Index View like
link_to 'join' CourseEnrollment.create(:course_id => course.id, :user_id => current_user)
Does this create method belong to my Model? I am confused because in my User Model I defined a method that uses role_assignments.create(.....). What is the difference between these 2 create methods? I cant use course_enrollments.create by the way. Thx for your time
I'm a bit confused as to what you're asking, but I'll try my best.
(First of all, in your example, current_user should probably be current_user.id.)
When you call CourseEnrollment.create, you are simply creating a new CourseEntrollment model with the specified attributes.
Assuming that your User model has_many :role_assignments:
When you call #role_assignments.create from within your User model, Rails automatically creates the association for you (e.g. sets the user_id to the id of the user). This doesn't have to be done within the model itself, though:
current_user.role_assignments.create(...) # automatically sets the association
Assuming that your User model also has_many :course_enrollments, the following will create a CourseEnrollment model and automatically associate it with the current user:
current_user.course_enrollments.create(...)