virtual attribute in a query - ruby-on-rails

User model has all_scores attribute and i created the method below
models/user.rb
def score
ActiveSupport::JSON.decode(self.all_scores)["local"]
end
What i'm trying to do this using this virtual attribute score to filter users. For example:
I need the users whose score is over 50. The problem is i can't use virtual attribute as a regular attribute in a query.
User.where("score > 50") # i can't do this.
Any help will be appreciated.

Well, the "easiest" solution would probably be User.all.select{|user| user.score > 50}. Obviously that's very inefficient, pulling every User record out of the database.
If you want to do a query involving the score, why don't you add a score column to the users table? You could update it whenever all_scores is changed.
class User < AR::Base
before_save :set_score, :if => :all_scores_changed?
protected
def set_score
self.score = ActiveSupport::JSON.decode(self.all_scores)["local"] rescue nil
end
end
This will also avoid constantly deserializing JSON data whenever you access user#score.

Related

Query records by class method

If I have a class method for a product returning its average rating, how do I query products with average_rating greater than a param I receive from frontend?
class Product < ApplicationRecord
def average_rating
ratings.sum(:rate) / ratings.count
end
end
For a scope I would need to pass the param from controller to model. Should I do that? Or should I just calculate the average rating in the controller instead? Or save average_rating in database with a callback when a rating is saved?
You cannot call class methods from SQL. You can however load all records into memory and do this, which is quite inefficient if you have a lot of records.
Product.all.select { |record| average_rating > record.value }
If this is a ruby method you can't do it (with SQL), unless you move your "average" logic into SQL too.
If the amount of records is a small one of the simplest solution is to use Ruby.
Product.all.select{|e| :&average_rating > e.value}

Increase a counter in parent when a child is added in has_many association

I have a schema where User has many Student with a user_id field.
In the User table, I am saving a counter next_student_number with default value as 1, and there is a roll_number column in Student.
In the Student class, I have before_create :generate_roll_number callback which sets the student's roll number to next_student_number and increments the value in User class.
Some thing like this :-
def generate_roll_number
self.roll_number = user.next_roll_number
user.increment! :next_roll_number
end
I feel there will be an issue when two records are trying to save at the same time here. Either they'll have a clash, or some roll numbers will be skipped.
What is the best way to implement this?
I think this should work fine:
Controller
def create
Student.transaction do
Student.create(user_id: current_user, ...)
end
end
Student Model
before_create :generate_roll_number
def generate_roll_number
user.increment! :next_roll_number
# Fires query like
# UPDATE users SET next_roll_number=2, WHERE id=xxx
self.roll_number = user.next_roll_number
end
Now, if any error happens while Student record is saved, the transaction will also rollback the incremented next_roll_number value in User table

Auto-create Association Object?

I have a Track model with an integer attribute called rank.
I'm updating the rank by specific actions: listens, downloads, purchases, ect. Ex: when a track is downloaded, in the track_controller I use track.increment!(:rank, by = 60)
I am thinking of creating an association model TrackRank so I can have a timestamp anytime a track's rank is updated (so I can do a rolling 3-week query of a track's rank for filtering and display purposes).
For every time a Tracks rank attr is updated, is there a way to auto-create an associated TrackRank object?
The ultimate goal:
Be able query the top X amount of tracks based on rank count in the last 3 weeks.
You can add a conditional call back on the update of the track
class Track < ActiveRecord::Base
# ignored associations and stuff
after_update :create_track_rank, if: :rank_changed?
# ignored methods
private
def create_track_rank
track_ranks.create(data)
end
end
You can update it on the controller
class TracksController < ApplicationController
def update
#track.update!
TrackRank.create(#track)
end
end

How can I update and save one model from another in Rails?

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

Virtual attributes in rails model

In my controller I'm calling #hour.shopper.add_product within a for loop.
My model looks like:
class Shopper < ActiveRecord::Base
attr_accessor :quantity
def add_product
if self.quantity.nil? || \
self.quantity == 0
self.quantity = 1
else
self.quantity += 1
end
self.save
end
end
When I print #hour.shopper.quantity it always says 'nil'. It seems like it's not saving the quantity attribute in the #hour.shopper object.
Thanks in advance!
Well, yes, instance variables aren't saved to the database (how could they be? There's no column for them).
Since the title of the question is "Virtual attributes", I'm going to assume that you don't have a quantity column in your database table (if you do, just remove the attr_accessor bit), however you still need to store the quantity somewhere if you want it to persist.
Usually virtual attributes are used when some attribute is not stored directly in the DB, but can be converted from and to an attribute that is. In this case it doesn't look like that is the case, so I can only recommend that you add a quantity column to your database table.

Resources