Find record ranking based on 2 columns - ruby-on-rails

I'm building a basic waiting list functionality for a project of mine.
I've got a model called Subscribers which has 2 columns: "points" and "created_at". Created_at is obviously generated when the record is created and points is an integer that starts from 1.
When users sign up they receive a code they can share on social media.
Every time a new user signs up through a referral code, the referrer gets 1 point.
I need a function to sort subscribers by points AND time so that, given a specific subscriber, I know how many people he has ahead and behind in the list.
The difficulty is that I'm not just counting the users with more points, but also the users who have signed up AFTER the specific user I'm querying. What I need to avoid is that users with the same points of a specific user, but who have registered much later, end up being ahead.
WaitingList model
class WaitingList < ActiveRecord::Base
# Database Schema
# t.string "name"
# t.string "uuid"
# t.integer "user_id"
# t.string "status", default: "active"
belongs_to :user
has_many :subscribers
validates :user, :presence => true
validates_presence_of :uuid, :name
end
Subscriber Model
class Subscriber < ActiveRecord::Base
# Database Schema
# t.string :email
# t.string :name
# t.integer :waiting_list_id
# t.string :code
# t.boolean :referred, default: false
# t.integer :referral_id
# t.integer "points", default: 1
belongs_to :waiting_list
validates :waiting_list, :presence => true
validates_presence_of :email
end
Any idea how to achieve this?

You can order the subscribers by their maximum points.
Subscriber ordered by their maximum points and ascending created_at.
#subscribers = Subscriber.group('id').order("max(points) desc, created_at")
Subscriber ordered by their maximum points and descending created_at.
#subscribers = Subscriber.group('id').order("max(points) desc, created_at desc")

Related

Connecting a shopping cart "total price" attribute to a payments configuration

I am starting to put together an application that includes, products, a shopping cart and payments. You start by adding the product to your cart by going to /products. Then if you navigate to /cart the system will populate your list of products ready to checkout. The plan is to link the "total price" attribute within the carts table into the payments table.
How do I go about linking two attributes from separate tables to make them the same? I have marked the two attributes that need to be the same, "total price" and "amount."
create_payments.rb
class CreatePayments < ActiveRecord::Migration
def change
create_table :payments do |t|
t.string :first_name
t.string :last_name
t.string :last4
***t.decimal :amount, precision: 12, scale: 3***
t.boolean :success
t.string :authorization_code
t.timestamps null: false
end
end
end
create_order_items.rb
class CreateOrderItems < ActiveRecord::Migration
def change
create_table :order_items do |t|
t.references :product, index: true, foreign_key: true
t.references :order, index: true, foreign_key: true
t.decimal :unit_price, precision: 12, scale: 3
t.integer :quantity
***t.decimal :total_price, precision: 12, scale: 3***
t.timestamps null: false
end
end
end
Please let me know if any further files will be needed to help troubleshoot the problem. Thank you in advance for any type of assistance!
I think what you're looking for here is to write a custom "getter" method, i.e. a virtual attribute. You could also overwrite save or use an (activerecord callback)[http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html]
For example:
class Payment < ActiveRecord::Base
has_many :order_items
# --------
# option 1
# --------
def amount
# sums up the total price of the associated order items
self.order_items.pluck(:price).sum
end
# --------
# option 2
# --------
def save(*args)
self.amount = self.order_items.pluck(:price).sum
super(*args)
end
# ----------
# option 3
# ----------
before_save :set_amount
def set_amount
self.amount = self.order_items.pluck(:price).sum
end
end
with the first option ("custom getter"), the aggregate column is not stored in the database and is dynamically recalculated each time it's value is accessed.
With the second option ("overriding save"), the amount column will be automatically set whenever save is called on a record.
The third option is probably the best in my opinion. It basically does the same thing as option 2, but looks a little cleaner.

how to sort ruby array by model action?

I'm trying to make a project like cafe search service based on social network, and I want to sort Cafe array by points which other users gave.
class Cafe < ActiveRecord::Base
belongs_to :user
has_many :posts, dependent: :destroy
has_many :tags, dependent: :destroy
has_many :payments, dependent: :destroy
has_many :payinfos, dependent: :destroy
mount_uploader :image,CafeimageuploaderUploader
mount_uploader :thumnail,CafeimageuploaderUploader
geocoded_by :address
after_validation :geocode
def avg
total = 0
posts.each do |c|
total += c.score
end
if posts.count == 0
0
else
total.to_f / posts.count
end
end
end
this is Cafe model, 'avg' is point average that users gave.
class CreatePosts < ActiveRecord::Migration
def change
create_table :posts do |t|
t.string :content , null: false, default: ""
t.string :image
t.string :address , null: false, default: "위치정보 없음"
t.string :hashstr
t.datetime :writtentime
t.integer :user_id
t.integer :cafe_id , null: false, default: 0
t.integer :score, default:0
t.timestamps null: false
end
end
end
Here's post columns.
What I want to do is sort new Cafe array by this avg action.(is it called model action, right??)
give me some advice, thank you.
I would add an attribute average_score to cafe model and a after_save callback method to post model. If a post will be safed you take all posts of a cafe (by the foreign key), calculate the average score and save it to the cafe or you trigger a method of the cafe model to do that. So you can sort your cafes easily.
Let use db query, it is more efficient. (assume Cafe's table name is cafes)
Cafe.joins("LEFT OUTER JOIN (SELECT cafe_id, AVG(score) AS avg_score
FROM posts
GROUP BY cafe_id
) AS temp
ON cafes.id = temp.cafe_id
").order('temp.avg_score DESC NULLS LAST')
Ideally, we calculate the average score in a temporary table first then join with Cafe via cafe_id. After that, we can easily use order with avg_score.
Because we are using LEFT OUTER JOIN, the avg_score of cafe which doesn't have any post will be NULL, so we use NULLS LAST to ensure that cafe will be in the end.

how to join an associated table with a has_one association

In my Rails app, I only require users to enter email and name upon signup, but then give them the option to provide fuller contact details for their profile. Therefore, I have a User.rb model that has an association with Contact.rb, namely,
User.rb
has_one :contact
Contact.rb
belongs_to :user
Contact.rb has the predictable fields you might expect such as address, postal code etc, but it also stores the province_id for a relation with the Province.rb model, so
Contact.rb
attr_accessible :address, :city, :mobile, :postalcode, :province_id, :user_id
belongs_to :user
belongs_to :province
Province.rb
has_many :contacts
I did it that way (rather than storing the name of the province as a "string" on contact.rb) so that I could more easily (so I thought) categorize users by province.
In the show action of one of the artists_controller, I do the following to check whether the user is trying to sort by province and then call an artists_by_province method that does a search
if params[:province_id]
province = params[:province_id]
province = province.to_i #convert string to integer
#artistsbyprovince = User.artists_by_province(province)
else
#artists = User.where(:sculptor => true)
end
This is the method on the User.rb model that it calls if a province id is passed in
scope :artists_by_province, lambda {|province|
joins(:contact).
where( contact: {province_id: province},
users: {sculptor: true})
}
However it gives me this error:
Could not find table 'contact'
If I make contacts plural
scope :artists_by_province, lambda {|province|
joins(:contacts).
where( contacts: {province_id: province},
users: {sculptor: true})
}
This error
Association named 'contacts' was not found; perhaps you misspelled it?
Can anyone tell me what I'm doing wrong when I'm making this query?
Update: I changed some of the details after posting because my copy and paste had some problems with it
P.S. ignore the fact that I'm searching for a 'sculptor.' I changed the names of the user types for the question.
from schema.rb
create_table "contacts", :force => true do |t|
t.string "firm"
t.string "address"
t.string "city"
t.string "postalcode"
t.string "mobile"
t.string "office"
t.integer "user_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.integer "province_id"
end
The problem was fixed by using contact (singular) in the join and contacts (plural) in the where clause. I'm guessing 'contact' (singular) reflects the has_one association between User.rb and Contact.rb, whereas 'contacts' is used in the where clause to represent the name of the table, which is always plural.
User.rb
has_one :contact
scope :artists_by_province, lambda {|province|
joins(:contact).
where( contacts: {province_id: province},
users: {sculptor: true})
}
Can you try the following?
scope :artists_by_province, lambda {|province|
joins(contact: {province: { id: province}}).
where(sculptor: true)
}

User models with different roles but discrete data?

I'm working on a Rails app where Users can have multiple roles, e.g. Manager, Employee, Admin, etc.
STI doesn't work in this situation, due to multiple roles, but I'd still like to keep role-related data in different tables if at all possible.
So for right now, my schema looks something like this:
create_table :roles do |t|
t.string :name
t.timestamps
end
create_table :users do |t|
t.string :first_name
t.string :last_name
t.string :email, :default => "", :null => false
t.timestamps
end
create_table :roles_users, :id => false do |t|
t.references :role, :user
end
And my User/Role models both have has_and_belongs_to_many relationships with each other.
So if, for example, I need the Manager to have_many Employees, is that possible with this setup? Is it possible for a User with the Manager role to have a Manager-specific attribute like secret_manager_information? Or do I need to re-think my approach?
Seeing as how Managers need to keep track of Employees (and in general other roles may need to keep track of other special data), I'd say that each role is different enough that they should get their own tables (assuming that you don't have too many roles).
For example, I would create a Manager and an Employee model:
class Manager
attr_accessible :user_id
has_many :employees
end
class Employee
attr_accessible :user_id, :manager_id
belongs_to :manager
end
Any user that is a Manager will have a record in the Manager table with user_id = user.id.
Any user that is an Employee will have a record in the Employee table with user_id = user.id and manager_id = (the id of the corresponding manager record)

Improve my data pull for a simple "announcements" rails app

I'm working through the RailsTutorial but making an "Announcements" webapp for the middle school I teach at (tweaking the given Twitter clone).
When users create an announcement, they use check boxes to determine which grades it should be displayed to (1-3 grades could be true). This is working correctly, with me storing grades as booleans.
create_table "announcements", :force => true do |t|
t.string "content"
t.integer "user_id"
t.boolean "grade_6"
t.boolean "grade_7"
t.boolean "grade_8"
t.date "start_date"
t.date "end_date"
t.datetime "created_at"
t.datetime "updated_at"
end
My users also have a grade field, which is an integer. I want to use this to make each user's home page show the announcements for their grade.
Example: An 8th grade teacher has grade = 8. When they log in, their home page should only show announcements which have grade_8 = TRUE.
Example: An principal has grade = 0. When they log in, their home page should show all announcements.
I'm struggling with how to translate the integer user.grade value into boolean flags for pulling announcements from the model.
The code I'm writing is working, but incredibly clunky. Please help me make something more elegant! I'm not tied to this db model, if you have a better idea. (In fact, I really don't like this db model as I'm hardcoding the number of grades in a number of locations).
# Code to pull announcements for the home page
def feed
case grade
when 6
grade_6
...
else
grade_all
end
end
# Example function to pull announcements for a grade
def grade_6
Announcement.where("grade_6 = ? AND start_date >= ? AND end_date <= ?",
TRUE, Date.current, Date.current)
the correct way to set this type of relationship up would be to use a many-to-many relationship via has_many through:
class Announcement < ActiveRecord::Base
has_many :announcement_grades
has_many :grades, :through => :announcement_grades
end
class AnnouncementGrades < ActiveRecord::Base
belongs_to :grade
belongs_to :announcement
end
class Grade < ActiveRecord::Base
has_many :announcement_grades
has_many :announcements, :through => :announcement_grades
end
then your migrations will be:
create_table :announcements, :force => true do |t|
t.date :start_date
t.date :end_date
t.timestamps #handy function to get created_at/updated_at
end
create_table :announcement_grades, :force => true do |t|
t.integer :grade_id
t.integer :announcement_id
t.timestamps
#start and end date might be more appropriate here so that you can control when to start and stop a particular announcement by grade rather than the whole announcement globally, depending on your needs.
end
create_table :grades, :force => true do |t|
t.timestamps
#now you have a bona-fide grade object, so you can store other attributes of the grade or create a relationship to teachers, or something like that
end
so, now you can simply find your grade then call announcements to filter:
#grade = Grade.find(params[:id])
#announcements = #grade.announcements
so, that's the correct way to do it from a modeling perspective. there are other considerations to this refactor as you will have to make significant changes to your forms and controllers to support this paradigm, but this will also allow for much greater flexibility and robustness if you decide you want to attach other types of objects to a grade besides just announcements. this railscast demonstrates how to manage more than one model through a single form using nested form elements, this will help you keep the look and feel the same after you apply the changes to your models. I hope this helps, let me know if you need more help doing this, it'll be a bit of work, but well worth it in the end.
Chris's example is theoretically superior. However, your original schema may be more practical if 1) you know your app won't become more complicated, and 2) the US's k-12 system is here to stay (i would bet on it...). If you would prefer to stick with the schema that you already have, here some improvements you could make to the code:
Let's add a 'grade' scope to your Announcement model
class Announcement < ActiveRecord::Base
....
scope :grade, lambda do |num|
num > 0 ? where("grade_#{num} = ?", true) : where('1=1')
end
....
end
This would allow for much simpler coding such as
teacher = User.find(user_id)
announcements = Announcement.grade(teacher.grade).where('start_date >= :today AND end_date <= :today', {:today => Date.today})

Resources