Get leaderboard to update every time user score updates - ruby-on-rails

I've managed to get the leaderboard gem working on my Rails app at the moment but I can't seem to figure out how to get the user data onto it. I also don't know how to get it to update as the user score updates...
I was following this tutorial to try and get it working but am now struggling getting the last bit integrated...
At the moment I have the Boards update_service.rb like this:
module Boards
class UpdateService < Boards::Base
def execute(user)
name = user.first_name
score = user.percentages
leaderboard.rank_member(name, score)
member = leaderboard.score_and_rank_for(name)
member[:page] = leaderboard.page_for(name, leaderboard.page_size)
member
end
end
end
I want to call this service on all the users now (so that they are all ranked with their current scores) and then to set it up so that this is called every time the user's score is updated.
I don't know where to call it to get the initial data...
But by playing around in the rails console I have found calling this works:
update = Boards::UpdateService.new
Student.all.each do |student|
update.execute(student)
end
For it to update as the user's score updates I thought I should do something like this:
after_update :update_rank
def update_rank
Boards::UpdateService.new
end
In the User class although to be honest I got this partially from the tutorial and I don't really understand why it's not:
def update_rank
Boards::UpdateService(user)
end
But that could just be because I really don't understand what's going on very well...

Because UpdateService is a class, not a method name, you have to create an instance of this class by invoking a constructor UpdateService.new. Seeing how there is an execute method there, consider doing something along the lines of:
def update_rank
update = Boards::UpdateService.new
update.execute(self)
end
UPDATE:
Calling execute will work for initialising simply because it updates user's leaderboard if it exists and creates it if it doesn't.
To initialise it properly you should consider invoking methods for creating the leaderbord for a user when he or she is being created.

Related

Ruby Review class creation

The Review class you see below represent a review that a user submitted for a product. Somewhere else in the code, Review.recent is called with a product_id, which is just a unique number that represents a single product. Fill in the code to make it work as expected!
Review.recent - This function should return the 5 most recent reviews (sorted by submit_time) with the specified product_id.
<=> - This special Ruby function is called when comparing two objects for sorting. It returns 1, 0, or +1 depending on whether the object is less than, equal to, or greater than the other object. You'll want to sort items by submit_time so recent items appear first.
set_submit_time - This function is called right before a review is created. We can use Ruby's Time class to set submit_time to now so we know when the review was created.
I'm new to ruby and I want this code for my very important work so how can I complete it help me please!
class Review < ApplicationRecord
# Every Review has a product_id and submit time
attr_accessor :product_id
attr_accessor :submit_time
# Before the new record is created, we'll call the :set_submit_time method
before_create :set_submit_time
def self.recent(product_id)
# Return only the 5 newest results for this product
# Reference: https://ruby-doc.org/core-2.4.2/Enumerable.html
Review.all
end
def <=>(other_review)
# Implement the comparison function for sorting
# Reference: http://ruby-doc.org/core-2.4.2/Comparable.html
end
private
def set_submit_time
# Set the submit_time
# Reference: https://ruby-doc.org/core-2.2.4/Time.html
end
end
self.recent
This is asking you to order by submit_time and return the first 5 results.
To perform the ordering, see: https://apidock.com/rails/ActiveRecord/QueryMethods/order
To perform the limit, see: https://apidock.com/rails/ActiveRecord/QueryMethods/limit
If you're still stuck on this problem, please show us what you've tried.
<=>
If you click the link in the comment you provided (http://ruby-doc.org/core-2.4.2/Comparable.html), the solution is almost identical to that example.
If you're still stuck on this problem, please show us what you've tried.
set_submit_time
It's worth having a quick look at: https://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html - to understand what is meant by a callback. Basically, this method is going to get automatically called whenever a new record is created. (You probably could have guessed this, based on the fairly self-explanatory name: before_create!)
Again, the first example on that page is almost identical to your scenario. You can use Time.now to get the current time.
If you're still stuck on this problem, please show us what you've tried.

rails variables in production environment

I am developing a rails app. Most of the parts work fine, but I got one weird problem when I tried to calculate the time an user used to edit and submit one form.
I thought it would be good to do it in the following order:
1. in the controller "edit" method, record the time the user start to see the form.
2. in the "update" method, record the submit time, then do the math and get how long the user had spent on the form.
class Test
##start_time = 0
##end_time = 0
def edit
##start_time = Time.now
end
def update
##end_time = Time.now
time_used = ##end_time - ##start_time
puts time_used.to_i
end
end
The code above actually works fine while running on my own developing computer, the output is what I expected. But when I upload the code to the production environment(multicore cpus), sometime the output is right, sometime it is not. I debugged the code and found in some case, the ##start_time is set to 0 when submitting the form. I am confused what was going on, maybe I just misused the ## for the variable. Please help me out, any idea would be appreciated, thanks.
Edit:
The problem is solved by adding a virtual attribute to the model as hinted by Vishal. In addition, I added a hidden field in the submit form, and in the strong parameter part added the corresponding parameter to allow it to be passed from edit to update method.
Your solution will create conflicts when more than two users try to edit simultaneously, So basically what idea I have is:
Add one virtual attribute in your model edit_start_time You don't need attribute for endtime because it can be directly fetched by Time.now at any time.
Set edit_start_time value in edit method like:
#model.edit_start_time = Time.now.utc #you can use any
In update method directly calculate edit time like:
total_update_time = Time.now.utc - #model.edit_start_time.utc
If you are unaware of how to create virtual attributes then there are so many questions on StackOverflow as well as docs. I am not explaining how to do it here because its the different topic.
All the best
You're using class variables that can interfer with each other. Your Test class will only ever have one class variable called ##start_time associated with it.
This means if another user sees the form, they will reset the ##start_time for every user currently on it.
To prevent this, use instance varaibles. When a new user sees the form, they will make a new instance variable that is tied to their instance of the class, rather than the class itself. This will allow users to have different start and end times.0
All you need to do is change every ## to #. So instead of ##start_time', try#start_time` throughout your code, and the same for end_time.

How do I calculate values for the current user in ruby on rails?

I have an application with a series of tests (FirstTest, SecondTest etc.)
Each test has a calculation set out its relevant model that gets calculated before being saved to the database set out like this:
#first_test.rb
before_save :calculate_total
private
def calculate_total
...
end
I then have an index page for each user (welcome/index) which displays the current user's results for each test. This all works fine, however I want to work out various other things such as each users average score overall etc.
Is it possible to access the current user from the welcome model?
Currently my welcome.rb is accessing the data follows:
#welcome.rb
def self.total
FirstTest.last.total
end
This obviously access the last overall test NOT the last test from the current user.
I feel like I may have just laid the whole application out in a fairly unintelligent manner...
Thanks in advance x
Well you need to save user_id in a column for each record in FirstTest. Then you can find the total for current user
FirstTest.where(:user_id => current_user.id).last.total

How Do I Store A Calculated Value In Rails

I am working on a project where I want to always show a post that has the least feedback (the goal is to encourage feedback on works in progress). I have figured out the calculation for this - although feedback is welcome - however, I am having trouble assigning this value to a column (:feedback_score) on the Post model. I need help.
post.rb:
class Post < ActiveRecord::Base
def feedback_score_calc
time_passed = ((Time.now - self.created_at)/1.hour).round(3)
feedback = self.comments.count
self.feedback_score = time_passed / feedback
end
end
I need to know how to call this method whenever a new comment is added, and I also need to be able to calculate it on some form of schedule. My goal is to display the least engaged on first visit with paging to progress to the 2nd to least engaged, 3rd, etc...
My other problem is that I can't even get this method to run through the console, I get a no method error, even when using def self.feedback_score_calc.
With your code as you've provided it, it's an instance method on Post. You should be able to do something like
#post = Post.find(some_id_here)
#post.feedback_score_calc
Creating it as a class method with definition def self.feedback_score_calc isn't what you want to do, because you're making calculations on a specific instance of Post and it's relations.
As #Kien Thanh mentioned in the comments, you'll need to call save on the Post instance after you set the column value if you want to see it reflected in the database.
#post = Post.find(some_id_here)
#post.feedback_score_calc
#post.save
or in the method itself
def feedback_score_calc
time_passed = ((Time.now - self.created_at)/1.hour).round(3)
feedback = self.comments.count
self.feedback_score = time_passed / feedback
save
end
Also worth mentioning, you can write to a column directly (bypassing the feedback_score= method ActiveRecord sets up) by calling write_attribute like this
def feedback_score_calc
time_passed = ((Time.now - self.created_at)/1.hour).round(3)
feedback = self.comments.count
write_attribute(:feedback_score, time_passed / feedback)
save
end
Finally, you need to either kill and restart your console or run reload! from within the console when making changes to your model that you're trying to verify from within the console.

Use find to initialize a constant?

Something like this:
class Category
SOME_CATEGORY = find_by_name("some category")
end
Category::SOME_CATEGORY
tried without a problem, but want to know if it is a bad idea, and the reasons if any..
thanks
If you don't want to hit the database each time you'll have to cache the model. There are several ways to do this, but one quick way is using Memoization. This was introduced in Rails 2.2.
class Category < ActiveRecord::Base
class << self
extend ActiveSupport::Memoizable
def named(name)
find_by_name(name)
end
memoize :named
end
end
Use it like this.
Category.named("some category") # hits the database
Category.named("some category") # doesn't hit the database
The cache should stay persistent across requests. You can reset the cache by passing true as the last parameter.
Category.named("some category", true) # force hitting the database
What do you want to do?
Maybe:
class Category
def self.some_category
Category.find_by_name("some category")
end
end
So you can call:
Category.some_category
=> <Category#2....>
It's not a terrible idea, but it's not really a good one either. It doesn't really fall in line with the way Rails does things. For one thing, you'll end up with a lot of ugly constant code. Too many ALL_CAPS_WORDS and your Ruby starts to look like C++. Bleah.
For another, it's inflexible. Are you going to make one of these constants for every category? If you add a new category two months from now, will you remember to update your Rails code, add a new constant, redeploy it and restart your server?
If it's important to you to be able to access categories very easily, and not repeat DB queries, here's a bit of metaprogramming that'll automatically look them up and create static methods like Lichtamberg's for you on first access:
def self.method_missing(category, *args) # The 'self' makes this a class method
#categories ||= {}
if (#categories[category] = find_by_name(category.to_s))
class_eval "def self.#{category.to_s}; #categories[#{category}]; end"
return #categories[category]
end
super
end
With this method in place, whenever you first call Category.ham, it'll create a class method that returns the value of find_by_name("ham") -- so that neither the query nor method_missing() runs again the next time you call it. This is pretty much the way the OpenStruct class works, BTW; look it up in the Pickaxe book if you want to learn more.
(Of course you'll still have the risk that, because these are all memoized, your Rails app won't reflect any changes you make to your category objects. This makes the assumption that changes won't happen or don't really matter. It's up to you to determine whether that assumption is valid for your app. You could always put an after_update callback in your code that resets ##categories if that's a problem; but at that point this starts to get complicated.)

Resources