If condition for time range - ruby-on-rails-3

I want to change the order amount based on the order delivery time, in my rails app. For this, I have modified my order_controller like this:
class OrdersController < ApplicationController
before_filter :authenticate_user!
def process_order
#order = current_order
if (#order.delievery_time[(4i), (5i)].between?('22:00', '00:30'))
#order.total = #order.total + ##mnc
end
end
end
Here, when user submits delivery time between 2200hrs to 0030hrs, 'total' field should be
(total = total + 50). And this total value should be submitted.
But giving 'syntax error on [(4i), (5i)]' error.
As I see in error log, it is displayed like this:
"delievery_time(1i)"=>"2013",
"delievery_time(2i)"=>"4",
"delievery_time(3i)"=>"9",
"delievery_time(4i)"=>"23",
"delievery_time(5i)"=>"00",
I am interested in last two values only. How to get this work?
Can anybody help?

Assuming delivery_time is of class Time (or something compatible like Rails TimeWithZone), you can access the hours and minutes like this:
delivery_time = Time.new(2013, 4, 9, 23, 00)
puts delivery_time.hour #=> 23
puts delivery_time.min #=> 0

Related

Rails 4 - query on association count is greater than an attribute

I want to do something like "Find all Books where book.pages.count < books.max_pages".
So the models are:
class Book
has_many :pages
end
class Page
belongs_to :book
end
I know I can find books w/ a set number of pages. eg:
# Get books w/ < 5 pages.
Book.joins(:pages).group("books.id").having("count(pages.id) < ?", 5)
Is there a good way to do this with a dynamic page count? eg:
Book.joins(:pages).group("books.id").select(.having("count(pages.id) <= book.max_pages")
If not I can always just store something inside the Book model (eg book.is_full = false until a save causes it to be full), but this is a bit less flexible if max_pages gets updated.
You could create a scope like this:
def self.page_count_under(amount)
joins(:pages)
.group('books.id')
.having('COUNT(pages.id) < ?', amount)
end
UPDATE
This should work if max_pages is an attribute of the Book model.
def self.page_count_under_max
joins(:pages)
.group('books.id')
.having('COUNT(pages.id) < books.max_pages')
end
Use counter_cache!
http://guides.rubyonrails.org/association_basics.html 4.1.2.3 :counter_cache

ActiveRecord find and only return selected columns aligned with [:id]

I have a set of data that is in two different tables. I want to join them up on my /show page in my ruby on rails app. In the controller, I have it set to find the date, set a variable, use that date to look through the other database to pull the needed information.
My controller looks like this:
def show
#ticket = Ticket.find(params[:id])
#hellodate = Ticket.select(:date)
#winnings = Winnings.find(:all, :conditions => {:date => #hellodate})
respond_to do |format|
format.html # show.html.erb
format.json { render json: #ticket }
end
end
for my #winnings to work, I need it to pull the row that has the date that matches with the #ticket date. I am new to this world and would love any input / solutions because this isn't working. It should only show one #winnings but it shows multiple though only ONE date will eve match. Thanks!
Your #hellodate is not what you think it is. This:
#hellodate = Ticket.select(:date)
will, more or less, give you the result of saying:
select "date" from "tickets"
so you'll get all Tickets but only the date columns will be pulled out of the database. Presumably you just want the date from #ticket:
#ticket = Ticket.find(params[:id])
#winnings = Winnings.where(:date => #ticket.date)

Parent entity sorted by votes difference

I have three models that look like this (I just left the stuff important for the question):
class Symbol < ActiveRecord::Base
has_many :mnemonic
end
class Mnemonic < ActiveRecord::Base
belongs_to :symbol
has_many :mnemonic_votes
end
class MnemonicVote < ActiveRecord::Base
belongs_to :mnemonic
attr_accessible :vote_up
end
:vote_up is of boolean type which if true means someone upvoted the mnemonic, and if false means someone downvoted it.
I would like to get top three mnemonics by vote difference. Let's say there are 5 mnemonic records in the database with the following number of up/down votes (MnemonicVote records with true/false as :vote_up field):
mnemonic up down total
mnemonic1 3 2 1
mnemonic2 17 3 14
mnemonic3 2 5 -3
mnemonic4 11 7 4
mnemonic5 5 5 0
I would like to get the following three mnemonics (with counts) by descending order:
mnemonic2 14
mnemonic4 4
mnemonic1 1
I wrote this actual code which gives me the result I want, but I am aware it sucks and I don't like how I did it because the data gets grouped and sorted after all the MnemonicVote records associated with a certaing Mnemonic record are fetched from the DB:
#mnemonics = Mnemonic.where(symbol_id: self.id) # here I fetch all Mnemonics associated with Symbol table
#mnemonics.sort_by { |mnemonic| mnemonic.votes_total }.reverse!
return #mnemonics.take(3)
where mnemonic.votes_total is a calculated attribute on Mnemonic object. I would like to get the same result by using a single AR (or even SQL) query. How can this be accomplished? Thanks.
I believe this is what you want:
Mnemonic.
joins(:mnemonic_votes).
select("mnemonics.*, SUM(IF(mnemonic_votes.upvote, 1, -1)) AS vote").
group("mnemonics.id").
order("vote DESC").
map { |m| [m.symbol, m.vote.to_i] }
Both answers were on the right track, except the IF clause that did not work with PostgreSQL (I would get function if(boolean, integer, integer) does not exist). Here is my final solution in case someone needs it:
Mnemonic.
select("mnemonics.*, SUM(CASE WHEN mnemonic_votes.vote_up THEN 1 ELSE -1 END) AS votes_total").
joins(:mnemonic_votes).
where(symbol_id: self.id).
group("mnemonics.id").
order("votes_total DESC").
limit(3)

Issues with DISTINCT when used in conjunction with ORDER

I am trying to construct a site which ranks performances for a selection of athletes in a particular event - I have previously posted a question which received a few good responses which me to identify the key problem with my code currently.
I have 2 models - Athlete and Result (Athlete HAS MANY Results)
Each athlete can have a number of recorded times for a particular event, i want to identify the quickest time for each athlete and rank these quickest times across all athletes.
I use the following code:
<% #filtered_names = Result.where(:event_name => params[:justevent]).joins(:athlete).order('performance_time_hours ASC').order('performance_time_mins ASC').order('performance_time_secs ASC').order('performance_time_msecs ASC') %>
This successfully ranks ALL the results across ALL athletes for the event (i.e. one athlete can appear a number of times in different places depending on the times they have recorded).
I now wish to just pull out the best result for each athlete and include them in the rankings. I can select the time corresponding to the best result using:
<% #currentathleteperformance = Result.where(:event_name => params[:justevent]).where(:athlete_id => filtered_name.athlete_id).order('performance_time_hours ASC').order('performance_time_mins ASC').order('performance_time_secs ASC').order('performance_time_msecs ASC').first() %>
However, my problem comes when I try to identify the distinct athlete names listed in #filtered_names. I tried using <% #filtered_names = #filtered_names.select('distinct athlete_id') %> but this doesn't behave how I expected it to and on occasions it gets the rankings in the wrong order.
I have discovered that as it stands my code essentially looks for a difference between the distinct athlete results, starting with the hours time and progressing through to mins, secs and msec. As soon as it has found a difference between a result for each of the distinct athletes it orders them accordingly.
For example, if I have 2 athletes:
Time for Athlete 1 = 0:0:10:5
Time for Athlete 2 = 0:0:10:3
This will yield the order, Athlete 2, Athlete1
However, if i have:
Time for Athlete 1 = 0:0:10:5
Time for Athlete 2 = 0:0:10:3
Time for Athlete 2 = 0:1:11:5
Then the order is given as Athlete 1, Athlete 2 as the first difference is in the mins digit and Athlete 2 is slower...
Can anyone suggest a way to get around this problem and essentially go down the entries in #filtered_names pulling out each name the first time it appears (i.e. keeping the names in the order they first appear in #filtered_names
Thanks for your time
If you're on Ruby 1.9.2+, you can use Array#uniq and pass a block specifying how to determine uniqueness. For example:
#unique_results = #filtered_names.uniq { |result| result.athlete_id }
That should return only one result per athlete, and that one result should be the first in the array, which in turn will be the quickest time since you've already ordered the results.
One caveat: #filtered_names might still be an ActiveRecord::Relation, which has its own #uniq method. You may first need to call #all to return an Array of the results:
#unique_results = #filtered_names.all.uniq { ... }
You should use DB to perform the max calculation, not the ruby code. Add a new column to the results table called total_time_in_msecs and set the value for it every time you change the Results table.
class Result < ActiveRecord::Base
before_save :init_data
def init_data
self.total_time_in_msecs = performance_time_hours * MSEC_IN_HOUR +
performance_time_mins * MSEC_IN_MIN +
performance_time_secs * MSEC_IN_SEC +
performance_time_msecs
end
MSEC_IN_SEC = 1000
MSEC_IN_MIN = 60 * MSEC_IN_SEC
MSEC_IN_HOUR = 60 * MSEC_IN_MIN
end
Now you can write your query as follows:
athletes = Athlete.joins(:results).
select("athletes.id,athletes.name,max(results.total_time_in_msecs) best_time").
where("results.event_name = ?", params[:justevent])
group("athletes.id, athletes.name").
orde("best_time DESC")
athletes.first.best_time # prints a number
Write a simple helper to break down the the number time parts:
def human_time time_in_msecs
"%d:%02d:%02d:%03d" %
[Result::MSEC_IN_HOUR, Result::MSEC_IN_MIN,
Result::MSEC_IN_SEC, 1 ].map do |interval|
r = time_in_msecs/interval
time_in_msecs = time_in_msecs % interval
r
end
end
Use the helper in your views to display the broken down time.

Limiting user votes in a ruby on rails app

I have an app where users can vote for entries. They are limited to a total number of votes per 24 hours, based on a configuration stored in my Setting model. Here's the code I'm using in my Vote model to check and see if they've hit their limit.
def not_voted_too_much?
#votes_per_period = find_settings.votes_per_period #how many votes are allowed per period
#votes = Vote.find_all_by_user_id(user_id, :order => 'id DESC')
#index = #votes_per_period - 1
if #votes.nil?
true
else
if #votes.size < #votes_per_period
true
else
if #votes[#index].created_at + find_settings.voting_period_in_hours.hours > Time.now.utc
false
else
true
end
end
end
end
When that returns, true -- they're allowed to vote. If false -- they can't. Right now, it relies on the records being retrieved in a certain order and that the one it selects is the oldest. This seems to work, but feels fragile to me.
I'd like to use :order => 'created_at DESC', but when I apply a limit to the query (I'd need to only get as many records as votes are allowed for that period), it seems to always pull the oldest records instead of the latest records and I'm not sure how to go about changing the query to pull the latest votes and not the oldest.
Any thoughts on the best way to go about this?
Can't you just count the user's votes which are newer than 24 hours old and check it against your limits? Am I missing something?
def not_voted_too_much?
votes_count = votes.where("created_at >= ?", 24.hours.ago).count
votes_count < find_settings.votes_per_period
end
(this is assuming you've got the votes association setup correctly in the user model)