User's position in Ruby on Rails, Devise gem - ruby-on-rails

I want to display a user's position on show page. For example, "Your position is 1921 out of 195119". Is there any effective way to do such thing in devise gem?
My idea was to add an extra column inside users table and make it auto increment for each entry. However, I think that there is a better way to do so. Thank you very much for your help and time.

Your User models have number of records and those are stored with id which is already auto incremented. Adding position similar to id is useless.
You can achieve it by following as few of your records used to get deleted and you want position as per existing records.
#position = User.all.ids.sort.index(#user.id)
Here #user is object for which 'show' action is getting called to get #position

If you have a column for position you probably need to update it if some of the records are deleted.
You can do two count queries, given a user object.
count_1 = User.where("id <= ?", user.id).count
count_1 is the position of the user
count_2 = User.count
count_2 is the total postions

Related

Add/Remove ActiveRecord associations without changing the db

I have a model Score and a model Adjustment. A Score has many Adjustments.
I also have a list of scores with adjustements that I wish to be able to update without affecting the db. I want to be able to do something like this:
scores.each do |score|
score.adjustments << some_new_adjustment
score.adjustments.delete_all(some_old_adjustments)
end
In the end, the scores array should change, but no query to the db should have been executed. How can I achieve this?
The guide shows no method to do that https://guides.rubyonrails.org/association_basics.html#has-many-association-reference
But you should be able to get the associated elements as an array, remove the one you don't want and then assign the new array. It shouldn't change the database until you save the parent.
current = score.ajustments.to_a
new_adjustments = current + new_adjustment - old_adjustment
score.adjustments = new_adjustments

Rails 5 get specific field/value from table

I have a table called group. I want this method to return just the content of the relevant record's ID field. At the moment it returns an active record object ID.
def get_group_name(group_id)
Group.select([:name]).where("id = ?", group_id)
end
Thanks in advance.
I think you can do easier with find
def get_group_name(group_id)
Group.find(group_id).name
end
This will get you only the name of the group.
def get_group_name(group_id)
Group.where(id: group_id).limit(1).pluck(:name).first
end
It will run this query:
SELECT name
FROM groups
WHERE id = ?
LIMIT 1;
A side note is, be careful of what you’re doing. Any time you have a method to get a single field’s value, while it can be more efficient at times, it can easily be misused. If you’re looping over a collection of group ids trying to grab all of the names, then you’d be better off 1 query up front for all of the names as opposed to 1 per group id on the page. So just keep and eye on your console and pay attention to the queries you’re running.
Also, if you are looking over a collection, you may want to look into includes for your ActiveRecord queries, to include the group data in the previous query. You can benchmark this all to figure out what’s fastest for your use case.

Retroactive changes to user records (point reconciliation)

I have three models:
Course
User
CourseCompletion
In addition to stuff like title and content, each course also has a point value. When a user completes a course, they are awarded the point value for the course, which is added to their total_points. CourseCompletion simply tracks which user has completed which courses (with columns user_id, course_id and completion_date).
One weakness with this data model is that if an admin user edits the point value of a course after a user has completed that course, the user's points are not updated. I need a way to do this retroactively. For example, if a user completes a course and earns 10 points, and then an admin changes the course to be worth 20 points, the user should have 20 points total in the end. I haven't done this sort of thing before - what would be the best approach?
My current plan is two-fold. In the first part, I make changes to the Course and CourseCompletion models:
Add a points_earned column to CourseCompletion that records how many points the user has earned for that completion.
Add a points_recently_changed column to Course. If a course's points are updated at any time, set this column to true for that course. (see my related question)
In the second part, a script or task (?) runs once per day and does the following:
Get all courses where points_recently_changed equals true
Find all course completions for those courses
Calculate the difference between course.points and course_completion.points_earned
Update the corresponding user's point total accordingly
Change course.points_recently_updated back to false.
Are there any glaring problems with this approach, or is there a "Rails Way" of doing stuff like this?
Why don't you use ActiveRecord::Calculations to get the sum of the points for the whole related courses and store them in the column. Update the column each time the admin does a change in the course points.
You can track changes in the points using ActiveRecord::Dirty
http://api.rubyonrails.org/classes/ActiveModel/Dirty.html
And calculate points using Calculations:
http://api.rubyonrails.org/classes/ActiveRecord/Calculations.html
As a possible solution:
class Course < ActiveRecord::Base
before_save :update_user_points
def update_user_points
User.all.each {|user| user.update_points } if self.points_changed?
end
end
class User < ActiveRecord::Base
def update_points
self.points = self.courses.sum(:points)
end
end
Suggestion:
I dislike saving the points in the database as it is a variable. I suggest you to do the calculation each time the user logins and keep it as cached number with expire date. So it has to be recalculated each day.
I tried Jorge's suggestion but was not satisfied with it. I ended up going with a similar approach whereby I recalculate a user's points during the login process.
User.rb
def recalculate_points
new_course_points = self.course_completion_courses.sum(:worth)
self.update_attribute(:points, self.course_completion_courses.sum(:worth))
self.save
end
session_helper.rb
def sign_in(user)
...
current_user.recalculate_points
end
Points are still stored in the User table - simply caching them doesn't work because I do some reporting that needs that information to persist in the database.

How would I calculate db column and display it in views in rails?

I'm trying to calculate the values of a column for given user. I have a column in my "joblist" table that is named "jobprice" and I would like to calculate the total of the "jobprice" column value for current_user. I've tried few ideas I've had but they didn't work. I've gotten everything related already. Just need to figure out how to locate all user_id of current_user for "joblist" then take "jobprice" column value and sum up the total.
Any suggestions?
Try using sum():
current_user.joblists.sum('jobprice')
This assumes you have declared a has_many :joblists association in your User model.

Custom Pagination with Sort, Rails 3

I have a rails 3 app that displays user submissions in order of how many votes they have. I have a model method called "rank" that calculates the score for each submission. Currently, when listing all the submissions I am using the following in my submissions_controller.rb
def index
#submissions = Submission.all.sort_by(&:rank).reverse
end
However, I want to add pagination to this, and it seems that neither 'will_paginate' or 'kamninari' will work properly here. This is because I need to sort the database columns by rank before paginating. Is there a better way to phrase my query so that pagination could be created with one of these gems, or do you know of a good custom pagination solution?
Thanks!
This ended up working:
#submissions = Submission.all.sort_by(&:rank).reverse
#submissions = Kaminari.paginate_array(#submissions).page(params[:page]).per(25)
Perhaps you could calculate rank in SQL? Or, add a column and save it with the record (e.g. before_save :calculate_rank).

Resources