Auto-create Association Object? - ruby-on-rails

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

Related

Updating associated record after update

Goal: to update associated model (Listing)
I have successfully (with help from SO) updated my listing.listing_status to a certain enum (3) if the listing.orders.count is above a certain threshold. I now need to update the listing status back to "live" (1) if the threshold is under a certain amount.
Models:
listing.rb
has_many :orders
order.rb
belongs_to :listings
has_one :order_1
has_one :order_2
order_1.rb
belongs_to :order
order_2.rb
belongs_to :order
order_1 and 2 are essentially variables for an order and store different types of files which need their own databases. When these are created, it then updates the associated Order's order_status to complete with an #order.update_column(order_status: n).
Issue: I would be able to do this within my order model like i have with updating when orders are above a certain threshold, but my issue is that... in my order_1 controller in the update method, I update the Order model with an update_column which skips callbacks. So now i need to do this through my order_1 and order_2 controllers (or models), I assume?
In the order_1 controller, I tried:
private
def update_listing
#order_seller = Order.where(order_status: [1]).where(listing_id: Listing.ids)
# #listing = Order.all.where(params[:listing_id])
# if #order_seller.count < 999
# #listing.update_column(:listing_status, 1)
# end
if #order_seller.count <= 999
#order_seller.listing.update! listing_status: 1
end
end
I receive an error saying the update_column method doesn't exist for the commented out section.
With the non-commented out section i get: undefined method listing
How can I update the Listings listing_status once an order_1 is created and the Listings orders are below a certain threshold?
check also callbacks after_create, after_update .., and move logic to the model

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

Rails use Boolean similar to counter_cache?

A Miniatures model has many Collections. Users can have and vote for the best Collection version of a Miniature. The votes are in a model called Imagevotes which update a counter_cache attribute in the Collections model.
What I want to do is flag Collections which are ranked first for a Miniature as GOLD, then rank the 2nd, 3rd and 4th as SILVER. I realise I can do this on the Miniature model by selecting the #miniature.collection.first, but I would like to be able to store that like you would store the vote-count in a counter_cache so that I could display the total number of GOLDS or SILVERS for any one user.
Is there a way that each model could have Boolean fields called GOLD and SILVER which would be updated as new votes are cast in the same way that a counter_cache is updated?
Any pointers and further reading much appreciated.
Update:
It occurs to me that this could also be done with a sort of second index column. A vote_position column if you will, that updated with a number from "1" for the record with the highest counter_cache number and ascended from there. Then I could use #miniature.collection.where(:vote_position => "1") or similar. Perhaps this is more ideal?
As it seems for me you just need to implement method in Miniature model:
def set_gold_and_silver
top_collections = self.collections.order("imagevotes_count desc").limit(4)
gold = top_collections.shift
gold.update_attribute :is_gold, true if gold
top_collections.each {|s| s.update_attribute :is_silver, true}
end
after that you can add it to after_create filter of Imagevotes model:
class Imagevotes < ActiveRecord::Base
after_create :set_gold_and_silver
def set_gold_and_silver
self.miniature.set_gold_and_silver
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 attribute in a query

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.

Resources