Rails 3.2
How do you do find a range to last record in the console?
Distributor.find_by_id(2..last)
Thank you
You can simply do:
Distributor.where('id >= 2')
This also works, but it performs 2 queries:
Distributor.where(id: 2..Distributor.last.id)
last(limit = nil)
Find the last record (or last N records if a parameter is supplied). If no order is defined it will order by primary key.
Person.last # returns the last object fetched by SELECT * FROM people
Person.where(["user_name = ?", user_name]).last
Person.order("created_on DESC").offset(5).last
Person.last(3) # returns the last three objects fetched by SELECT * FROM people.
http://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html
def last(limit = nil)
if limit
if order_values.empty? && primary_key
order(arel_table[primary_key].desc).limit(limit).reverse
else
to_a.last(limit)
end
else
find_last
end
end
https://github.com/rails/rails/blob/4e823b61190388219868744a34dcfe926bad511c/activerecord/lib/active_record/relation/finder_methods.rb#L157
Related
I have two models associated with each other.
For example an Order contains many Order Items.
If there is a match (ie: already an order item with the same sku), I'd like to increment an order item quantity. Is this the best way to do it in Ruby?
switch = false
order.order_items.each do |item|
if item.sku == test.sku
item.increment!(:quantity)
switch = true
end
end
I was originally trying to do something like:
if order.order_items.where(sku: test.sku).length > 0
order_item = order.order_items.where("sku = ?", test.sku).take
order_item.increment!(:quantity)
end
but I ended up having some errors.
Thanks
You can do it using one query without fetching the data and iterating through it, as below:
order.order_items.where(sku: test.sku).update_all("quantity = quantity + 1")
NB:
update_all skip the validation & callbacks
I have slightly modified version for Kaminari to find on which page my record is (original code is here: https://github.com/kaminari/kaminari/wiki/FAQ#how-can-i-know-which-page-a-record-is-on):
module KaminariHelper
extend ActiveSupport::Concern
# Detecting page number in pagination. This method allows you to specify column name, order,
# items per page and collection ids to filter specific collection.
#
# Options:
# * :by - specifies the column name. Defaults to :id.
# * :order - specifies the order in DB. Defaults to :asc.
# * :per - specifies amount of items per page. Defaults to object model's default_per_page.
# * :nulls_last - if set to true, will add "nulls last" into the order.
# * :collection_ids - array of ids for the collection of current class to search position in specific collection.
# If it's ommited then search is done across all objects of current object's model.
#
def page_num(options = {})
column = options[:by] || :id
order = options[:order] || :asc
per = options[:per] || self.class.default_per_page
nulls_last = options[:nulls_last] == true ? "nulls last" : ""
operator = (order == :asc ? "<=" : ">=")
data = if options[:collection_ids].present?
self.class.where(id: options[:collection_ids])
else
self.class
end
# 1. Get a count number of all the results that are listed before the given record/value.
# 2. Divide the count by default_per_page or per number given as an option.
# 3. Ceil the number to get a proper page number that the record/value is on.
(
data.where("#{column} #{operator} ?", read_attribute(column))
.order("#{column} #{order} #{nulls_last}").count.to_f / per
).ceil
end
end
However when I test it for some weird reasons .order doesn't seem to be executed. Here is the sql output in rails console:
2.3.1 :005 > email.page_num(by: :sent_at, order: :desc, per: 25, collection_ids: user.emails.ids, nulls_last: true)
(1.1ms) SELECT "emails"."id" FROM "emails" WHERE "emails"."deleted_at" IS NULL
AND "emails"."user_id" = $1 [["user_id", 648]]
(1.5ms) SELECT COUNT(*) FROM "emails" WHERE "emails"."deleted_at" IS NULL AND
"emails"."id" IN (35946, 41741) AND (sent_at >= '2016-01-22 14:04:26.700352')
=> 13
Why does .order is not applied in the final SQL query? Is there a way to execute it? Otherwise the code doesn't make sense since there are no guarantee it'll give me proper page number.
Rails does ignore the order clause when counting.
Instead of relying on Rails' count method, try counting manually:
data.where("#{column} #{operator} ?", read_attribute(column))
.order("#{column} #{order} #{nulls_last}")
.select('COUNT(*) c').first.c.to_f / per
Noob here, I'm trying to query my SQLite database for entries that have been made in the last 7 days and then return them.
This is the current attempt
user.rb
def featuredfeed
#x = []
#s = []
Recipe.all.each do |y|
#x << "SELECT id FROM recipes WHERE id = #{y.id} AND created_at > datetime('now','-7 days')"
end
Recipe.all.each do |d|
#t = "SELECT id FROM recipes where id = #{d.id}"
#x.each do |p|
if #t = p
#s << d
end
end
end
#s
end
This code returns each recipe 6(total number of objects in the DB) times regardless of how old it is.
#x should only be 3 id's
#x = [13,15,16]
if i run
SELECT id FROM recipes WHERE id = 13 AND created_at > datetime('now','-7 days')
1 Rows returned with id 13 is returned
but if look for an id that is more than 7 days old such as 12
SELECT id FROM recipes WHERE id = 12 AND created_at > datetime('now','-7 days')
0 Rows returned
I'm probably over complicating this but I've spent way too long on it at this point.
the return type has to be Recipe.
To return objects created within last 7 days just use where clause:
Recipe.where('created_at >= ?', 1.week.ago)
Check out docs for more info on querying db.
Edit according to comments:
Since you are using acts_as_votable gem, add the votes caching, so that filtering by votes score is straightforward:
Recipe.where('cached_votes_total >= ?', 10)
Ruby is expressive. I would take the opportunity to use a scope. With Active Record Scopes, this query can be represented in a meaningful way within your code, using syntactic sugar.
scope :from_recent_week, -> { where('created_at >= ?', Time.zone.now - 1.week) }
This allows you to chain your scoped query and enhance readability:
Recipe.from_recent_week.each do
something_more_meaningful_than_a_SQL_query
end
It looks to me that your problem is database abstraction, something Rails does for you. If you are looking for a function that returns the three ids you indicate, I think you would want to do this:
#x = Recipe.from_recent_week.map(&:id)
No need for any of the other fluff, no declarations necessary. I also would encourage you to use a different variable name instead of #x. Please use something more like:
#ids_from_recent_week = Recipe.from_recent_week.map(&:id)
Orders have many line_items, which in turn have an employee_id and amount_cents. In the second line of this query, I try creating a line_item_perc variable by gathering all the line_items with a certain employee_id and dividing their amounts_cents by all the other line_items:
Order.joins(:tips, :line_items)
.select('line_items WHERE(custom_attributes -> "employee_id" = ?) AS employee_line_items,
SUM(employee_line_items.amount_cents) / SUM(line_items.amount_cents) AS line_item_perc',
params[:employee_id])
.sum('tips.amount_cents * line_item_perc')
Unfortunately, this doesn't work. The select statement doesn't even appear in the final SQL. (I'm assuming this is because sum overwrites it.) So what else could I do to get line_item_perc?
def line_item_percent(employee_id, line_item_id)
total_amount_in_cents = Order.joins(:line_items).where("employee_id = ?", employee_id).pluck(:amount_cents).sum
line_item_cents = LineItem.find(line_item_id).amount_cents
answer = line_item_cents / total_amount_in_cents
end #returns decimal
Try this. Did it off top of head with rails not SQL.
In Ruby on Rails, I'm trying to order the matches of a player by whether the current user is the winner.
The sort order would be:
Sort by whether the current user is the winner
Then sort by created_at, etc.
I can't figure out how to do the equivalent of :
Match.all.order('winner_id == ?', #current_user.id)
I know this line is not syntactically correct but hopefully it expresses that the order must be:
1) The matches where the current user is the winner
2) the other matches
You can use a CASE expression in an SQL ORDER BY clause. However, AR doesn't believe in using placeholders in an ORDER BY so you have to do nasty things like this:
by_owner = Match.send(:sanitize_sql_array, [ 'case when winner_id = %d then 0 else 1 end', #current_user.id ])
Match.order(by_owner).order(:created_at)
That should work the same in any SQL database (assuming that your #current_user.id is an integer of course).
You can make it less unpleasant by using a class method as a scope:
class Match < ActiveRecord::Base
def self.this_person_first(id)
by_owner = sanitize_sql_array([ 'case when winner_id = %d then 0 else 1 end', id])
order(by_owner)
end
end
# and later...
Match.this_person_first(#current_user.id).order(:created_at)
to hide the nastiness.
This can be achived using Arel without writing any raw SQL!
matches = Match.arel_table
Match
.order(matches[:winner_id].eq(#current_user.id).desc)
.order(created_at: :desc)
Works for me with Postgres 12 / Rails 6.0.3 without any security warning
If you want to do sorting on the ruby side of things (instead of the SQL side), then you can use the Array#sort_by method:
query.sort_by(|a| a.winner_id == #current_user.id)
If you're dealing with bigger queries, then you should probably stick to the SQL side of things.
I would build a query and then execute it after it's built (mostly because you may not have #current_user. So, something like this:
query = Match.scoped
query = query.order("winner_id == ?", #current_user.id) if #current_user.present?
query = query.order("created_at")
#results = query.all