How Do I Store A Calculated Value In Rails - ruby-on-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.

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.

Why I cannot access to this instance variable (in model) from another method?

I have a method in my model and I call it with before_create:
def first_position
if [...]
[...]
else
#last_known = Picture.where(user_id: user_id).order('position desc').first
self.position = nil
end
end
And I have another method that I call with after_commit:
def default_position
pictures = Picture.where(user_id: user_id).where('created_at > ?', #last_known.created_at)
i = #last_known.position + 1
pictures.each do |pic|
pic.position = i
i += 1
end
end
But in default_position, #last_known returns nil. Do you know why?
EDIT:
Well, I discovered that I have two commit, but the one that concerns the picture is second, so #last_known is set at the first commit but disappears at the second commit.
Initially I thought that ActiveRecord reloads the record sometime before the after_commit, or at least reinitializes it. I've checked with Rails 5.2.1 and nothing happens with instance variables, they remain correctly set. This means that (unless you're using older Rails), there simply are no pictures for the given user and your code does not seem to handle that.
Also, your after_commit will run after you update the object as well, which might be the issue as your variable will not be set in that case.

Get leaderboard to update every time user score updates

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.

Rails 4: Create object only in a factory method?

Going to simplify a bit here, but assume an app that has Users and UserRecords. A User must have one or more UserRecords. I want to limit the creation of UserRecords to a method in User, namely #create_new_user_record.
In other words, I don't want to allow UserRecord.new or UserRecords.create anywhere else in the application. I need to control the creation of these records, and perform some logic around them (for example, setting the new one current and any others to not current), and I don't want any orphaned UserRecords in the database.
I tried the after_initialize callback and checking if the object is new and raising an error there, but of course I do need to call UserRecord.new in User#create_new_user_record. If I could somehow flag in #create_new_user_record that I am calling new from that method, and pick that up in after_intialize, that would work, but how?
I might be over thinking it. I can certainly create a that method on User, and just 'know' to always call it. But others will eventually work on this app, and I will go away and come back to it as some point.
I suppose I could raise the error and just rescue from it in #create_new_user_record. Then at least, if another develop tries it elsewhere they will find out why I did it when they pursue the error.
Anyway, wondering what the Rails gurus here had to say about it.
super method is what you are looking for. Though you'll need some workaround (maybe simple check for value of option only you know about) to fit your needs
class User < ActiveRecord:Base
def .new(attributes = nil, options = {})
do_your_fancy_stuff
if option[:my_secret_new_method]
super # call AR's .new method and automatically pass all the arguments
end
end
Ok, here's what I did. Feel free to tell me if this is bad idea or, if it's an ok idea, if there's a better way. For what it's worth, this does accomplish my goal.
In the factory method in the User model, I send a custom parameter in the optional options hash defined on the new method in the API. Then I in the UserRecord#new override, I check for this parameter. If it's true, I create and return the object, otherwise I raise in custom error.
In my way of thinking, creating a UserRecord object any other way is an error. And a developer who innocently attempts it would be lead to explanatory comments in the two methods.
One thing that's not clear to me is why I need to leave off the options hash when I call super. Calling super with it causes the ArgumentError I posted in my earlier comment. Calling super without it seems to work fine.
class User < ActiveRecord::Base
...
def create_new_user_record
# do fancy stuff here
user_record = UserRecord.new( { owner_id: self.id, is_current: true }, called_from_factory: true )
user_record.save
end
...
end
class UserRecord < ActiveRecord::Base
...
def UserRecord.new(attributes = nil, options = {})
if options[:called_from_factory] == true
super(attributes)
else
raise UseFactoryError, "You must use factory method (User#create_new_user_record) to create UserRecords"
end
end
...
end

Resources