Reducing database hits in Rails - ruby-on-rails

I have two models, projects and words where project has_many :words (words is really just a model that holds the quantity of words written each day for each project.
I have a view that I build like this, which shows all the days from start to end in the project and how many, if any words were written on that day:
<% project_range(#project.start, #project.end).each do |day| %>
<%= day %>
<%= get_word_count_by_date(#project, day ) %>
<% end %>
And in my helper:
def project_range(start, finish)
project_days = (start..finish).collect
end
def get_word_count_by_date(project, date)
word_count = Word.find_by_project_id_and_wrote_on(project, date)
if word_count
word_count.quantity
else
0
end
end
Trouble is, in the view, that hits my database a lot. For example, if the project is 30 days, I get:
Word Load (0.2ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-01' LIMIT 1
Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-02' LIMIT 1
Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-03' LIMIT 1
Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-04' LIMIT 1
Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-05' LIMIT 1
Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-06' LIMIT 1
Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-07' LIMIT 1
Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-08' LIMIT 1
Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-09' LIMIT 1
Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-10' LIMIT 1
Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-11' LIMIT 1
Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-12' LIMIT 1
Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-13' LIMIT 1
Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-14' LIMIT 1
Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-15' LIMIT 1
Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-16' LIMIT 1
Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-17' LIMIT 1
Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-18' LIMIT 1
Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-19' LIMIT 1
Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-20' LIMIT 1
Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-21' LIMIT 1
Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-22' LIMIT 1
Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-23' LIMIT 1
Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-24' LIMIT 1
Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-25' LIMIT 1
Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-26' LIMIT 1
Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-27' LIMIT 1
Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-28' LIMIT 1
Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-29' LIMIT 1
Word Load (0.1ms) SELECT "words".* FROM "words" WHERE "words"."project_id" = 2 AND "words"."wrote_on" = '2011-09-30' LIMIT 1
Is there a way to do this without querying every single day in the length of the project? I tried starting by loading all of a project's words first, but couldn't figure out how to get the days with zero in there.

This is a "n+1" problem... What you want to do is join words and projects in your query so that all the words for each project are included in the result set.
Assuming that your project "has_many :words":
#project = Project.find(:id, :include => :words)
Now the words collection on each project will be pre-populated with the words in just 1 query.
Read more under the "Eager Loading of Associations" http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html

You could use a block helper to keep it clean and avoid looking them up:
def project_range(project, start, finish, &blk)
words = project.words.where(:wrote_on => start..finish)
word_map = words.index_by(&:wrote_on)
for day in start..finish
word_count = word_map[day] ? word_map[day].quantity : 0
blk.call(day, word_count)
end
end
Then use it like
<% project_range(project, start, finish) do |day, word_count| %>
<%= day %>
<%= word_count %>
<% end %>
You could also clean up the helper a bit (avoid having SQL in it), maybe by passing the list of pre-fetched words or using a scope
EDIT: m_x suggested the start..finish where clause on wrote_on which is cleaner!

I'd go for something like:
#words = #project.words.where("wrote_on >= ? and wrote_on <= ?", start, end)
and than use group_by to display them in the view:
#words.group_by(&:wrote_on).each do |day, word|
<%= day %>
<%= word.quantity %>
end

Related

How to fetch records from last 7 days with padding to 0 if no records found?

I'm trying to fetch records from the last 7 days (may change to support month and year) to populate a chart, and I need to pad the days where no records where found to 0. I first tried grouping but found no way of padding the data, so I came up with this:
today = Date.today
array_data = (today - 6.days..today).map do |day|
total_time = #project.time_entries.where(created_at: (day.beginning_of_day..day.end_of_day)).map(&:duration_hours).sum
{ day.strftime("%a %d %b") => total_time }
end
#entries_data = array_data.reduce Hash.new, :merge
The result is a hash with a day as a key and the sum of a calculated property as the value.
This however, requires a database query for each day:
TimeEntry Load (1.1ms) SELECT "time_entries".* FROM "time_entries" WHERE "time_entries"."project_id" IN ('bd9f541c-e37a-45de-bc94-28bef2b5eade')
TimeEntry Load (0.4ms) SELECT "time_entries".* FROM "time_entries" WHERE "time_entries"."project_id" = $1 AND ("time_entries"."created_at" BETWEEN '2016-03-21 04:30:00.000000' AND '2016-03-22 04:29:59.999999') [["project_id", "bd9f541c-e37a-45de-bc94-28bef2b5eade"]]
TimeEntry Load (0.2ms) SELECT "time_entries".* FROM "time_entries" WHERE "time_entries"."project_id" = $1 AND ("time_entries"."created_at" BETWEEN '2016-03-22 04:30:00.000000' AND '2016-03-23 04:29:59.999999') [["project_id", "bd9f541c-e37a-45de-bc94-28bef2b5eade"]]
TimeEntry Load (0.2ms) SELECT "time_entries".* FROM "time_entries" WHERE "time_entries"."project_id" = $1 AND ("time_entries"."created_at" BETWEEN '2016-03-23 04:30:00.000000' AND '2016-03-24 04:29:59.999999') [["project_id", "bd9f541c-e37a-45de-bc94-28bef2b5eade"]]
TimeEntry Load (0.2ms) SELECT "time_entries".* FROM "time_entries" WHERE "time_entries"."project_id" = $1 AND ("time_entries"."created_at" BETWEEN '2016-03-24 04:30:00.000000' AND '2016-03-25 04:29:59.999999') [["project_id", "bd9f541c-e37a-45de-bc94-28bef2b5eade"]]
TimeEntry Load (0.5ms) SELECT "time_entries".* FROM "time_entries" WHERE "time_entries"."project_id" = $1 AND ("time_entries"."created_at" BETWEEN '2016-03-25 04:30:00.000000' AND '2016-03-26 04:29:59.999999') [["project_id", "bd9f541c-e37a-45de-bc94-28bef2b5eade"]]
TimeEntry Load (0.6ms) SELECT "time_entries".* FROM "time_entries" WHERE "time_entries"."project_id" = $1 AND ("time_entries"."created_at" BETWEEN '2016-03-26 04:30:00.000000' AND '2016-03-27 04:29:59.999999') [["project_id", "bd9f541c-e37a-45de-bc94-28bef2b5eade"]]
TimeEntry Load (0.5ms) SELECT "time_entries".* FROM "time_entries" WHERE "time_entries"."project_id" = $1 AND ("time_entries"."created_at" BETWEEN '2016-03-27 04:30:00.000000' AND '2016-03-28 04:29:59.999999') [["project_id", "bd9f541c-e37a-45de-bc94-28bef2b5eade"]]
How can I rework this to make it send only 1 query to the database?
Not certain this is what you mean, but here's what I think you need to do:
Run the query getting all data for the last N days.
Group these records by the day
Merge this on top of a hash with values of { date => 0} for each of the last N days
Ok, as tpbowden mentioned in his answer, I managed to reduce it to 1 query like this:
date_format = "%d %b"
entries_last_week = time_entries.group_by { |t| t.created_at.to_date.strftime(date_format) }.map { |k, v| { k => v.map(&:duration_hours).sum } }
entries_hash = entries_last_week.reduce Hash.new, :merge
today = Date.today
days = (today - 6.days..today).map { |day| { day.strftime(date_format) => 0 } }
hash_of_days = days.reduce Hash.new, :merge
hash_of_days.merge(entries_hash)

Rails 4 how to include polymorphic

I have a Webcam model which has many Urls
class Webcam < ActiveRecord::Base
has_many :urls, -> { where('kind LIKE ?','preview_url')}, :as => :urlable, :dependent => :destroy
has_one :preview_url, -> { where('kind LIKE ?', 'preview_url') }, :as => :urlable, :dependent => :destroy, :class_name => 'Url'
end
class Url < ActiveRecord::Base
belongs_to :urlable, polymorphic: true
end
So Webcam.joins(:preview_url).where(:id=>cam_ids) does work, but I still have a sql call for each cam.
Url Load (0.2ms) SELECT "urls".* FROM "urls" WHERE "urls"."urlable_id" = $1 AND "urls"."urlable_type" = $2 AND (kind LIKE 'preview_url') AND "urls"."kind" = $3 ORDER BY "urls"."id" ASC LIMIT 1 [["urlable_id", 9756], ["urlable_type", "Webcam"], ["kind", "preview_url"]]
CACHE (0.0ms) SELECT "urls".* FROM "urls" WHERE "urls"."urlable_id" = $1 AND "urls"."urlable_type" = $2 AND (kind LIKE 'preview_url') AND "urls"."kind" = $3 ORDER BY "urls"."id" ASC LIMIT 1 [["urlable_id", 9756], ["urlable_type", "Webcam"], ["kind", :preview_url]]
Url Load (0.2ms) SELECT "urls".* FROM "urls" WHERE "urls"."urlable_id" = $1 AND "urls"."urlable_type" = $2 AND (kind LIKE 'preview_url') AND "urls"."kind" = $3 ORDER BY "urls"."id" ASC LIMIT 1 [["urlable_id", 9759], ["urlable_type", "Webcam"], ["kind", "preview_url"]]
CACHE (0.0ms) SELECT "urls".* FROM "urls" WHERE "urls"."urlable_id" = $1 AND "urls"."urlable_type" = $2 AND (kind LIKE 'preview_url') AND "urls"."kind" = $3 ORDER BY "urls"."id" ASC LIMIT 1 [["urlable_id", 9759], ["urlable_type", "Webcam"], ["kind", :preview_url]]
Url Load (0.2ms) SELECT "urls".* FROM "urls" WHERE "urls"."urlable_id" = $1 AND "urls"."urlable_type" = $2 AND (kind LIKE 'preview_url') AND "urls"."kind" = $3 ORDER BY "urls"."id" ASC LIMIT 1 [["urlable_id", 9760], ["urlable_type", "Webcam"], ["kind", "preview_url"]]
CACHE (0.0ms) SELECT "urls".* FROM "urls" WHERE "urls"."urlable_id" = $1 AND "urls"."urlable_type" = $2 AND (kind LIKE 'preview_url') AND "urls"."kind" = $3 ORDER BY "urls"."id" ASC LIMIT 1 [["urlable_id", 9760], ["urlable_type", "Webcam"], ["kind", :preview_url]]
How can I avoid these many sql calls?
You should use .includes(:urls, :preview_url) (not .joins) to avoid N+1 queries problem (see Rails Guides on the topic).
You should also have a separate urls.kind value for preview_url because you can't distinguish it from other urls now.

order active_admin column by parent item in belongs_to relationship

I have two models: show_request and show. A show_Request belongs_to a show and a show has_many show_requests. On the show_request page in active_admin, I want to order show_requests by the show's created_at value. Here is my code so far:
ActiveAdmin.register ShowRequest do
controller do
def scoped_collection
end_of_association_chain.includes(:show)
#I also tried ShowRequest.includes(:show)
end
end
index do
column 'Show', sortable: "shows.created_at_asc" do |show_req|
link_to show_req.show.name, admin_show_path(show_req.show)
end
end
end
Here are the server logs:
Started GET "/admin/show_requests" for 127.0.0.1 at 2015-09-18 09:35:36 -0400
Processing by Admin::ShowRequestsController#index as HTML
AdminUser Load (0.3ms) SELECT "admin_users".* FROM "admin_users" WHERE "admin_users"."id" = 1 ORDER BY "admin_users"."id" ASC LIMIT 1
(1.2ms) SELECT COUNT(*) FROM "show_requests" WHERE (not_going_to_show = 'f' OR i_want_my_horse_to_compete = 't')
(0.2ms) SELECT COUNT(*) FROM "show_requests"
(0.2ms) SELECT COUNT(*) FROM "show_requests" WHERE (not_going_to_show = 't' AND i_want_my_horse_to_compete = 'f')
(0.3ms) SELECT COUNT(count_column) FROM (SELECT 1 AS count_column FROM "show_requests" WHERE (not_going_to_show = 'f' OR i_want_my_horse_to_compete = 't') LIMIT 30 OFFSET 0) subquery_for_count
CACHE (0.0ms) SELECT COUNT(count_column) FROM (SELECT 1 AS count_column FROM "show_requests" WHERE (not_going_to_show = 'f' OR i_want_my_horse_to_compete = 't') LIMIT 30 OFFSET 0) subquery_for_count
CACHE (0.0ms) SELECT COUNT(*) FROM "show_requests" WHERE (not_going_to_show = 'f' OR i_want_my_horse_to_compete = 't')
CACHE (0.0ms) SELECT COUNT(count_column) FROM (SELECT 1 AS count_column FROM "show_requests" WHERE (not_going_to_show = 'f' OR i_want_my_horse_to_compete = 't') LIMIT 30 OFFSET 0) subquery_for_count
ShowRequest Load (2.0ms) SELECT "show_requests".* FROM "show_requests" WHERE (not_going_to_show = 'f' OR i_want_my_horse_to_compete = 't') ORDER BY "show_requests"."id" desc LIMIT 30 OFFSET 0
Show Load (9.7ms) SELECT "shows".* FROM "shows" WHERE "shows"."id" IN (2, 1)
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", 2]]
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", 1]]
CACHE (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", 1]]
Show Load (0.2ms) SELECT "shows".* FROM "shows"
User Load (0.2ms) SELECT "users".* FROM "users"
This is not working. It is not affecting the order of the columns at all. How do I fix this?
Take a look at this part of the log:
ShowRequest Load (2.0ms) SELECT "show_requests".* FROM "show_requests" WHERE (not_going_to_show = 'f' OR i_want_my_horse_to_compete = 't') ORDER BY "show_requests"."id" desc LIMIT 30 OFFSET 0
Show Load (9.7ms) SELECT "shows".* FROM "shows" WHERE "shows"."id" IN (2, 1)
You can see that the ShowRequests and Shows are loaded in separate queries. sortable is not going to work here, because you can't order one table by a field of another without a join. The fix should be to tell ActiveRecord that you need to reference the shows table in your query:
controller do
def scoped_collection
super.includes(:show).references(:shows)
end
end
index do
column :show, sortable: 'shows.created_at'
end
references only works with includes, and forces Rails to perform a join when eager loading.

How to make update a few rows in the table one minimalnym number of database queries

Have a collection of categories, represented as collections of objects. Each object has a sort property. This property takes a numeric value from 1 to the number which is the last element of the collection. There is a list of categories filtered by the sort property
linecategories=Linecategory.eager_load(:main_image).order('sort')
Then this list is dragged drag and drap and a certain number of objects to the collection is changing the value of this property sort. Further ajax goes from 2 to n of objects whose properties of the sort has changed.
Task to do update the changed fields in the database.
Now data comes in the form of:
data={}
data['items']=params[:data]
Where data has the form
data: [{id: 1, name: "Гороскопы", slug: "horoscopes", title: "keywords-horoscopes",…},…]
0: {id: 1, name: "Гороскопы", slug: "horoscopes", title: "keywords-horoscopes",…}
created_at: "2015-01-10T21:14:56.000Z"
description: "description-horoscopes"
id: 1
keywords: "keywords-horoscopes"
name: "Гороскопы"
slug: "horoscopes"
sort: 1
title: "keywords-horoscopes"
updated_at: "2015-07-19T19:10:03.000Z"
1: {id: 5, name: "Гадания", slug: "divination", title: "eywords-divination",…}
created_at: "2015-01-11T08:47:10.000Z"
description: "2015-01-11 08:47:10"
id: 5
keywords: "description-divination"
main_image: {id: 1, src: "images/categories_images/devination.jpg", linecategory_id: 5, created_at: null,…}
name: "Гадания"
slug: "divination"
sort: 2
title: "eywords-divination"
updated_at: "2015-07-19T19:24:23.000Z"
2: {id: 3, name: "Вкусности", slug: "delicious", title: "keywords-delicious",…}
created_at: "2015-01-10T21:17:28.000Z"
description: "2015-01-10 21:17:28"
id: 3
keywords: "description-delicious"
name: "Вкусности"
slug: "delicious"
sort: 3
title: "keywords-delicious"
updated_at: "2015-07-19T19:24:23.000Z"
Now I implemented the update like so:
def updates
data={}
data['items']=params[:data]
data['items'].each { |el|
#item=Linecategory.find(el['id'])
#item.update_attributes(el.permit(:sort))
}
render json: {update: 1, data: data['items']}
end
But I think it is not the optimal solution, I think it can be done more efficiently and more beautiful by means of ruby on rails and activerecords whether this is so, and if possible suggest solutions to the problem?
def updates
data=params[:data].as_json(only: [:id, :sort])
h = {}
data.each_with_index { |e, i| h[e['id']] = e }
Linecategory.update(h.keys, h.values)
render json: {update: 1, data: h}
end
Mine has found solutions, I still don't katsa completely optimal, since before the update I have to use:
data.each_with_index { |e, i| h[e['id']] = e }
but even so it works.
Plausible queries to update the database continues to be set, but girlfriend can do it?
Linecategory Load (0.4ms) SELECT `linecategories`.* FROM `linecategories` WHERE `linecategories`.`id` = 6 LIMIT 1
(0.3ms) BEGIN
Linecategory Exists (0.4ms) SELECT 1 AS one FROM `linecategories` WHERE (`linecategories`.`slug` = BINARY 'our_dreams' AND `linecategories`.`id` != 6) LIMIT 1
SQL (0.2ms) UPDATE `linecategories` SET `sort` = 1, `updated_at` = '2015-07-20 21:07:47' WHERE `linecategories`.`id` = 6
(2.3ms) COMMIT
Linecategory Load (0.5ms) SELECT `linecategories`.* FROM `linecategories` WHERE `linecategories`.`id` = 4 LIMIT 1
(0.3ms) BEGIN
Linecategory Exists (0.7ms) SELECT 1 AS one FROM `linecategories` WHERE (`linecategories`.`slug` = BINARY 'beauty_and_health' AND `linecategories`.`id` != 4) LIMIT 1
SQL (0.3ms) UPDATE `linecategories` SET `sort` = 2, `updated_at` = '2015-07-20 21:07:47' WHERE `linecategories`.`id` = 4
(1.0ms) COMMIT
Linecategory Load (0.2ms) SELECT `linecategories`.* FROM `linecategories` WHERE `linecategories`.`id` = 1 LIMIT 1
(0.2ms) BEGIN
Linecategory Exists (0.7ms) SELECT 1 AS one FROM `linecategories` WHERE (`linecategories`.`slug` = BINARY 'horoscopes' AND `linecategories`.`id` != 1) LIMIT 1
SQL (0.2ms) UPDATE `linecategories` SET `sort` = 3, `updated_at` = '2015-07-20 21:07:47' WHERE `linecategories`.`id` = 1
(0.9ms) COMMIT
Linecategory Load (0.4ms) SELECT `linecategories`.* FROM `linecategories` WHERE `linecategories`.`id` = 2 LIMIT 1
(0.3ms) BEGIN
Linecategory Exists (0.4ms) SELECT 1 AS one FROM `linecategories` WHERE (`linecategories`.`slug` = BINARY 'test_yourself' AND `linecategories`.`id` != 2) LIMIT 1
SQL (0.5ms) UPDATE `linecategories` SET `sort` = 4, `updated_at` = '2015-07-20 21:07:48' WHERE `linecategories`.`id` = 2
(0.9ms) COMMIT
Linecategory Load (0.3ms) SELECT `linecategories`.* FROM `linecategories` WHERE `linecategories`.`id` = 5 LIMIT 1
(0.2ms) BEGIN
Linecategory Exists (0.5ms) SELECT 1 AS one FROM `linecategories` WHERE (`linecategories`.`slug` = BINARY 'divination' AND `linecategories`.`id` != 5) LIMIT 1
SQL (0.2ms) UPDATE `linecategories` SET `sort` = 5, `updated_at` = '2015-07-20 21:07:48' WHERE `linecategories`.`id` = 5
(0.8ms) COMMIT
Linecategory Load (0.3ms) SELECT `linecategories`.* FROM `linecategories` WHERE `linecategories`.`id` = 7 LIMIT 1
(0.1ms) BEGIN
Linecategory Exists (0.4ms) SELECT 1 AS one FROM `linecategories` WHERE (`linecategories`.`slug` = BINARY 'what_is_it_in_my_name' AND `linecategories`.`id` != 7) LIMIT 1
SQL (0.2ms) UPDATE `linecategories` SET `sort` = 6, `updated_at` = '2015-07-20 21:07:48' WHERE `linecategories`.`id` = 7
(0.9ms) COMMIT
Linecategory Load (0.3ms) SELECT `linecategories`.* FROM `linecategories` WHERE `linecategories`.`id` = 3 LIMIT 1
(0.2ms) BEGIN
Linecategory Exists (0.4ms) SELECT 1 AS one FROM `linecategories` WHERE (`linecategories`.`slug` = BINARY 'delicious' AND `linecategories`.`id` != 3) LIMIT 1
SQL (0.3ms) UPDATE `linecategories` SET `sort` = 7, `updated_at` = '2015-07-20 21:07:48' WHERE `linecategories`.`id` = 3
(1.0ms) COMMIT
I found better method
# /admin_categories PUT
def updates
data=params[:data].as_json(only: [:id, :sort])
db = ActiveRecord::Base.connection()
ids=[]
query = "UPDATE linecategories SET sort = CASE id "
data.each_with_index do |e, i|
query += ActiveRecord::Base.send(:sanitize_sql_array,["WHEN ? THEN ? ", e['id'], e['sort']])
ids.push e['id']
end
query += "END "
query += "WHERE id in (#{ids.join(",")}) "
db.execute(query)
render json: {}
end

active record quering in rails 4 and postgres

I have 2 models
Class Ride
has_many :trips
#state (string: active or expired)
end
Class Trip
#date (Date attribute)
scope :active, -> (start_at = Date.today) { where("trips.date >= ?", [Date.today, start_at].max) }
end
Daily, I need update state on Rides with active state having all trips with date attribute < Date.today
how to perform this in 1 query?
i can archive such result using:
Ride.with_active_state.select{|r| r.trips.active.size ==0}
but it makes huje queries to count trips, eq:
[1] pry(main)> Ride.with_active_state.select{|r| r.trips.active.size ==0}
(7.3ms) SELECT f_geometry_column,coord_dimension,srid,type FROM geometry_columns WHERE f_table_name='rides'
Ride Load (1.6ms) SELECT "rides".* FROM "rides" WHERE (rides.workflow_state = 'active')
(2.9ms) SELECT f_geometry_column,coord_dimension,srid,type FROM geometry_columns WHERE f_table_name='trips'
(1.3ms) SELECT COUNT(*) FROM "trips" WHERE "trips"."ride_id" = $1 AND (trips.date >= '2013-09-24') [["ride_id", 9]]
(0.7ms) SELECT COUNT(*) FROM "trips" WHERE "trips"."ride_id" = $1 AND (trips.date >= '2013-09-24') [["ride_id", 10]]
(0.7ms) SELECT COUNT(*) FROM "trips" WHERE "trips"."ride_id" = $1 AND (trips.date >= '2013-09-24') [["ride_id", 11]]
(0.7ms) SELECT COUNT(*) FROM "trips" WHERE "trips"."ride_id" = $1 AND (trips.date >= '2013-09-24') [["ride_id", 12]]
(0.8ms) SELECT COUNT(*) FROM "trips" WHERE "trips"."ride_id" = $1 AND (trips.date >= '2013-09-24') [["ride_id", 13]]
(0.8ms) SELECT COUNT(*) FROM "trips" WHERE "trips"."ride_id" = $1 AND (trips.date >= '2013-09-24') [["ride_id", 14]]
(0.5ms) SELECT COUNT(*) FROM "trips" WHERE "trips"."ride_id" = $1 AND (trips.date >= '2013-09-24') [["ride_id", 15]]
(0.5ms) SELECT COUNT(*) FROM "trips" WHERE "trips"."ride_id" = $1 AND (trips.date >= '2013-09-24') [["ride_id", 16]]
(0.5ms) SELECT COUNT(*) FROM "trips" WHERE "trips"."ride_id" = $1 AND (trips.date >= '2013-09-24') [["ride_id", 17]]
(0.5ms) SELECT COUNT(*) FROM "trips" WHERE "trips"."ride_id" = $1 AND (trips.date >= '2013-09-24') [["ride_id", 18]]
(0.5ms) SELECT COUNT(*) FROM "trips" WHERE "trips"."ride_id" = $1 AND (trips.date >= '2013-09-24') [["ride_id", 19]]
(0.5ms) SELECT COUNT(*) FROM "trips" WHERE "trips"."ride_id" = $1 AND (trips.date >= '2013-09-24') [["ride_id", 20]]
....
Add scopes on Ride with a group and having clause. It would check the count of all future trips of a ride and return the rides with 0 count.
Class Ride
scope :active_state, where(state: "active")
scope :with_nonactive_trips, -> (start_date = Date.today){ joins(:trips).
group("rides.id").
having( ["sum(trips.date > ?) = 0",start_date] ) }
end
Ride.active_state.with_nonactive_trips
# returns All the rides with state == active, alteast one trip and having no trips with date > Date.today
Using a lambda since you had it on the active scope in Trip. I am guessing you need to use a different date than Date.today for some queries.

Resources