I need some advice on a voting system in rails that recognizes the top vote getter on a monthly basis. I have a system that works but being new to rails, I'm sure there are more efficient methods available. Below is a simplified version of my current setup(controller code omitted):
class Charity < ActiveRecord::Base
has_many :votes
end
class Vote < ActiveRecord::Base
belongs_to :charity
end
My schema is as follows:
ActiveRecord::Schema.define(:version => 20130310015627) do
create_table "charities", :force => true do |t|
t.string "name"
t.text "description"
t.date "last_win"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "votes", :force => true do |t|
t.integer "charity_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
end
I'll be using the 'whenever' gem to run a cron job to determine the monthly winner and update the 'last_win' column of the charities table.
The following code is where I'm questioning my efficiency:
vote_counts = Vote.count(:group => "charity_id")
most_votes = vote_counts.values.max
winning_ids = vote_counts.map{|k,v| v == most_votes ? k :nil }.compact
charities = Charity.find(winning_ids)
charities.each {|charity| charity.update_attributes(:last_win => Date.today)}
I'm sure there are many ways to do this better and would appreciate some suggestions. If you have suggestions on better ways to set up the votes table / associations, that would be appreciated too.
Thanks in advance,
CRS
Something like this:
If there was only one winner, this would work I think
winner_id = Vote.group(:charity_id).order("count(*) desc").pluck(:charity_id).first
Charity.find(winner)id).update_attribute!(:last_win => Date.today)
You could modify it for ties:
most_votes = Vote.group(:charity_id).order("count(*) desc").count.first[1]
winners = Vote.group(:charity_id).having("count(*) = ?", most_votes).pluck(:charity_id)
Charity.where(:id => winners).update_all(:last_win => Date.today)
Make sure everything is indexed correctly in your database,
You can probably streamline it more, but the SQL is going to get more complicated.
The last two lines could be:
Charity.where(id:winning_ids).update_all(last_win:Date.today)
Which would translate into a single SQL update command, instead of issuing an update command for each winning charity.
The first part where you identify the winning charities looks okay, and since you're running it as a cron job you probably don't care if it takes a few minutes.
However, if you'd like to show the values in real time, you could add an after_create hook on Vote to update a counter for its owner charity (possibly in another table):
class Vote < ActiveRecord::Base
belongs_to :charity
after_create :increment_vote_count
CharityVote.where(year:Time.now.year, month:Time.now.month,
charity_id:self.charity_id).first_or_create.increment!(:counter)
end
Related
I'm using PostgreSQL with Rails in API mode and I have a row called expired with data type Date and I want to make a trigger that identifies if the expired date is equal to the current date and change my is_expired column from false to true, but I don't know where to start.
I've read a bit of Rails documentation or some libraries like hairtrigger and it seems a bit confusing.
this is my table:
class CreateRifs < ActiveRecord::Migration[7.0]
def change
create_table :rifs do |t|
t.string :name
t.datetime :rif_date
t.string :award_with_sign
t.string :award_no_sign
t.string :plate
t.integer :year
t.float :price
t.integer :numbers
t.integer :with_signs
t.date :expired
t.boolean :is_expired, default: false
t.timestamps
end
end
end
Do you have a specific reason to use a database column for this? Because you could easily write this method on the model:
def expired? # This is actually an override, see next paragraph
expired <= Date.today
end
Or alternatively, if the expired column only gets populated with a past date after it actually has expired (and doesn't, e.g., represent a future expiry), don't write a method at all. Rails automatically provides you with a predicate for every column: a column? method that returns true if column is populated.
You don't need a trigger for this, maybe a scope to only get expired vs not expired records.
class Rif < ApplicationRecord
scope :expired, -> { where('expired < NOW()') }
end
You can then use the scope later on
expired_rifs = Rif.expired
I have two models: Draft and Pick. I want the Draft's ActiveRecord column current_pick to increase by 1 after a Pick is created.
Inside Pick, I have a method that increase draft.current_pick by 1:
class Pick < ActiveRecord::Base
belongs_to :draft
after_save :advance_draft
def advance_draft
draft.updraft
end
Inside draft, updraft is:
def updraft
self.current_pick += 1
end
My test to ensure the current_pick is being increased by one is:
it 'should run the advance_draft method after creating' do
team1 = FactoryGirl.create(:team)
team2 = FactoryGirl.create(:team_two)
cam = FactoryGirl.create(:player)
draft = FactoryGirl.create(:two_team_draft)
pick = Pick.create(:player_id => cam.id, :team_id => draft.team_at(draft.current_pick).id, :draft_id => draft.id, :draft_position => draft.current_pick)
draft.draft_position.should eq 2
end
The pick and draft are being created in the test but the updraft method is not being called on the correct draft because the pick.draft.draft_position remains at 1 after the pick has been created. (it should increase to 2).
Here is my schema:
create_table "drafts", force: true do |t|
t.integer "draft_position"
t.integer "number_of_teams"
t.integer "PPTD"
t.integer "PPR"
t.integer "current_pick", default: 1
end
create_table "picks", force: true do |t|
t.integer "player_id"
t.integer "team_id"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "draft_id"
t.integer "draft_position"
end
My question is, how do I properly increase the pick.draft.current_pick inside my test?
I would do 2 things, use increment! when updating the count to increment the value and save the record, and reload the object you're looking at, since its database representation has changed since it was created.
def updraft
increment!(:current_pick)
end
This will update the current_pick and save the object in one shot.
it 'should run the advance_draft method after creating' do
# ... setup
draft.reload.draft_position.should eq 2
end
Now, instead of using the instance of draft that was created before the modification occurred, it's using a version current with the database.
UPDATE (specific and more detailed previous version is below):
I'm developing a TV station web site. Here are requirements for my Program section:
Each Program has ONE Category.
Each Program has ONE Subcategory.
Each Category has MANY Subcategories
Each Category has MANY Programs.
Each Subcategory has ONE Category
Each Subcategory has MANY Programs.
I want to retrieve all these three models to be associated. For example, I should be able to retrieve below data from my views:
While:
p = Program.find(1)
p_cat = ProgramCategory.find(1)
p_subcat = ProgramSubcategory.find(1)
I should be able to retrieve and also EDIT these:
p.program_category
p.program_subcategory
or
program_category.programs
program_subcategory.programs
You can see what I tried below to achieve these requirements. You may recommend me a totally different way or fix my mistakes.
Thank you
============================================================
I have 3 models. They are supposed to be nested in eachother.
ProgramCategory > ProgramSubcategory > Program
Here are my codes:
ProgramCategory model:
has_many :programs
has_many :program_subcategories
ProgramSubcategory model:
belongs_to :program_category
has_many :programs
Program Model:
belongs_to :program_category
belongs_to :program_subcategory
As I create a new Program, I can set its Category and everything is fine. I can access them from both sides. For example,
program.program_category
gives me what I expected. and also
program_category.programs
gives me what I want to have, too.
BUT, -here comes the question-
When I try to access program.program_subcategory, I receive just a nil.
Eventhough my Subcategory's Category is set and my Program's Category is set too, why I can't access program.program_subcategory directly?
When I type program_category.program_subcategories, I receive all Subcategories owned by that Category. But I CAN NOT get Subcategories from directly a Program object.
My scheme is below. Any help is appriciated.
ActiveRecord::Schema.define(:version => 20120926181819) do
create_table "program_categories", :force => true do |t|
t.string "title"
t.text "content"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "program_subcategories", :force => true do |t|
t.integer "program_category_id"
t.string "title"
t.text "content"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
add_index "program_subcategories", ["program_category_id"], :name => "index_program_subcategories_on_program_category_id"
create_table "programs", :force => true do |t|
t.integer "program_category_id"
t.integer "program_subcategory_id"
t.string "title"
t.text "content"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
add_index "programs", ["program_category_id", "program_subcategory_id"], :name => "my_join1", :unique => true
end
The design is strange a bit. If you need nesting like
ProgramCategory > ProgramSubcategory > Program
then you need
class Program < ActiveRecord::Base
belongs_to :program_subcategory
end
class ProgramSubcategory < ActiveRecord::Base
belongs_to :program_category
has_many :programs
end
class ProgramCategory < ActiveRecord::Base
has_many :programs, :through => :program_subcategories
has_many :program_subcategories
end
This way when you create a program you can assign a subcategory to it. And this subcategory is already assigned to category, so you can access it via program.program_subcategory.program_category
And you do not need program_category_id foreign key in programs because program is not connected to category directly, but via subcategory.
UPDATE
Each Program has ONE Category.
Each Program has ONE Subcategory.
Each Category has MANY Subcategories
Each Category has MANY Programs.
Each Subcategory has ONE Category
Each Subcategory has MANY Programs.
Then I believe that my answer is still valid. You see, my structure is the same as your description except Each Program has ONE Category (because rails has no belongs_to through). you has one is actually belongs_to (because it can belong to only one).
But as soon as Each Program has ONE Subcategory and Each Subcategory has ONE Category program's subcategory's category will be the ONLY program's category. You can have p.program_category by defining a method on a Program class:
def program_category
program_subcategory.program_category
end
Now for the part of
I should be able to retrieve and also EDIT these:
p.program_category
Imagine you have a Program in subcategory Comedy from category Movies.
You say you want to be able to EDIT programs category directly (if I understood correctly), like this:
p.program_category = ProgramCategory.find_by_name("Sports")
But what you expect to be program's subcategory then? As soon as Sports have many subcategories? Do you expect it to be blank?
So in this design the only way to change program's category is to change program's subcategory:
p.program_subcategory = ProgramSubcategory.find_by_name("Tennis")
And this will manke program's category == Sports, because Tennis belongs to Sports.
Note: If you really want sometimes to change program's category directly, leaving its subcategory blank it requires another design of course. I do not think it is very difficult but it requires more work with less help from Rails AR Associations magic.
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})
I have entries table with a content field which might contain a lot of text. In most cases I don't need to access that field, so it seems to be a big waste of resources to every time load a huge amount of unused data from the database (select * from entries where id = 1).
How could I specify the default_scope, that all the fields apart from content would be loaded from database?
Assuming Rails 3 and a schema that looks like this:
create_table "entries", :force => true do |t|
t.string "title"
t.text "content"
t.datetime "created_at"
t.datetime "updated_at"
end
You can use the select method to limit the fields that are returned like this:
class Entry < ActiveRecord::Base
default_scope select([:id, :title])
end
In the rails console, you should see something like this:
puts Entry.where(:id => 1).to_sql # => SELECT id, title FROM "entries" WHERE "entries"."id" = 1
When you do want to select all of the fields, you can use the unscoped method like this:
puts Entry.unscoped.where(:id => 1).to_sql # => SELECT * FROM "entries" WHERE "entries"."id" = 1
As scoping, and default_scope mainly, gives many problems in use, my best way is to move big contents (binary, very large texts) to separate table.
create_table "entries", :force => true do |t|
t.string "title"
# ... more small sized attributes of entries
t.timestamps
end
create_table "entry_contents", :force => true do |t|
t.references :entries, foreign_key: true
t.text "content"
t.timestamps
end
class Entry ...
# reference
has_one :entry_content
# build entry_content for every new entry record
after_initialize do |entry|
entry.build_entry_content unless entry.entry_content.present?
end
end
This limits loading big data only when needed.
Entry.find(1).entry_content.content
To build on #phlipper answer's, if you want to just specify one or a few columns to get rid of:
class Entry < ActiveRecord::Base
default_scope { select(Entry.column_names.map!(&:to_sym) - [:metadata]) }
end
As you can see, as of Rails 5+ you have to pass a block to default_scope.
Also, you should consider not using default scope
Its not a default scope, but I am using the following solution for my case:
scope :no_content, -> { select(self.column_names.map(&:to_sym) - [:content]) }
Add no_content to the call is imo not a big deal, because you probably know which calls are a problem.