Rails - n+1 queries even though association is preloaded - ruby-on-rails

I have a Person class that has many Book(s), and each Book has one Bookmark.
In my code I'm doing something like this:
items = author.books.includes(:bookmark)
items.each { |item| generate_hash(item) }
def generate_hash(item)
{ ... rest ..., finished: item.try(:finished?) }
end
class Book
def finished?
bookmark ? bookmark.page == self.pages : false
end
end
Using item.try(:finished?) triggers a select query to get each individual bookmark even though I've preloaded them. However, if I change it to finished: item.is_a?(Book) ? item.finished? : nil it triggers only one query to get all the bookmarks just as I planned. Can anyone make sense of this?
EDIT - adding the queries I'm getting in the log:
with item.try(:finished?):
Bookmark Load (2.7ms) SELECT `bookmarks`.* FROM `bookmarks` WHERE `bookmarks`.`deleted_at` IS NULL AND `bookmarks`.`book_id` = 41 LIMIT 1
Bookmark Load (2.3ms) SELECT `bookmarks`.* FROM `bookmarks` WHERE `bookmarks`.`deleted_at` IS NULL AND `bookmarks`.`book_id` = 42 LIMIT 1
Bookmark Load (3.9ms) SELECT `bookmarks`.* FROM `bookmarks` WHERE `bookmarks`.`deleted_at` IS NULL AND `bookmarks`.`book_id` = 43 LIMIT 1
Bookmark Load (3.3ms) SELECT `bookmarks`.* FROM `bookmarks` WHERE `bookmarks`.`deleted_at` IS NULL AND `bookmarks`.`book_id` = 44 LIMIT 1
# with item.is_a?(Book) ? item.finished? : nil
Bookmark Load (2.6ms) SELECT `bookmarks`.* FROM `bookmarks` WHERE `bookmarks`.`deleted_at` IS NULL AND `bookmarks`.`book_id` IN (41, 42, 43, 44)

Related

Custom filter activeadmin is not working

I am trying to make a custom filter with active admin. This is my code where I setting hard code.
filter :versions_created, as: :date_range, label: "Resolvido em"
ransacker :versions_created, formatter: proc { |v,w|
v_date = "2016-09-09".to_date
w_date = "2016-09-09".to_date
time_range = v_date ..w_date
results = Occurrences.where({created_at:time_range} ).map(&:id)
results.present? ? results : nil
}do |parent|
parent.table[:id]
end
This apparently works because my log say:
//IS CORRECT
Occurrence Load (0.6ms) SELECT "occurrences".* FROM "occurrences" WHERE ("occurrences"."created_at" BETWEEN '2016-09-09' AND '2016-09-09')
CACHE (0.0ms) SELECT "occurrences".* FROM "occurrences" WHERE ("occurrences"."created_at" BETWEEN '2016-09-09' AND '2016-09-09')
//HEREEEE THE PROBLEM
*(0.5ms) SELECT COUNT(*) FROM "occurrences" WHERE ("occurrences"."id" >= NULL AND "occurrences"."id" <= NULL)
(0.6ms) SELECT COUNT(count_column) FROM (SELECT 1 AS count_column FROM "occurrences" WHERE ("occurrences"."id" >= NULL AND "occurrences"."id" <= NULL) LIMIT 30 OFFSET 0) subquery_for_count*
My question is do you have any idea why the two other queries have values with NULL?

group by in view or controller

i am trying to loop through a list of products which a supplier sells via its different variants. i can get the list of products to display, but i wish to group these by the product id as to only display it once.
in my controller i have
#supplier = Supplier.joins(products: :variants).find(params[:id])
in my view i have
- #supplier.variants.group_by(&:product_id).each do |product_id, item|
= render :partial => 'product', :locals => {:item => item }
and my partial
= link_to shopping_supplier_path(item) do
%li.mdl-list__item.mdl-list__item--three-line
%span.mdl-list__item-primary-content
%span= item.product.name
%span.mdl-list__item-text-body
= item.product.description.downcase
%span.mdl-list__item-secondary-content
%i.material-icons
chevron_right
%hr
which when the sql executes returns the following query
Started GET "/shopping/suppliers/latte-cartelle-drive-thru-coffee-241---245-princes-hwy--ha-1" for 127.0.0.1 at 2016-04-19 23:22:08 +1000
Processing by Shopping::SuppliersController#show as HTML
Parameters: {"id"=>"latte-cartelle-drive-thru-coffee-241---245-princes-hwy--ha-1"}
User Load (0.6ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT 1 [["id", 1]]
Supplier Load (32.7ms) SELECT "suppliers".* FROM "suppliers" WHERE "suppliers"."permalink" = $1 ORDER BY "suppliers"."id" ASC LIMIT 1 [["permalink", "latte-cartelle-drive-thru-coffee-241---245-princes-hwy--ha-1"]]
Supplier Load (41.9ms) SELECT "suppliers".* FROM "suppliers" INNER JOIN "variant_suppliers" ON "variant_suppliers"."supplier_id" = "suppliers"."id" INNER JOIN "variants" ON "variants"."id" = "variant_suppliers"."variant_id" INNER JOIN "products" ON "products"."id" = "variants"."product_id" INNER JOIN "variants" "variants_products" ON "variants_products"."product_id" = "products"."id" WHERE "suppliers"."permalink" = $1 ORDER BY "suppliers"."id" ASC LIMIT 1 [["permalink", "latte-cartelle-drive-thru-coffee-241---245-princes-hwy--ha-1"]]
Variant Load (0.9ms) SELECT "variants".* FROM "variants" INNER JOIN "variant_suppliers" ON "variants"."id" = "variant_suppliers"."variant_id" WHERE "variant_suppliers"."supplier_id" = $1 [["supplier_id", 1]]
Rendered shopping/suppliers/_product.html.haml (53.5ms)
error
NoMethodError at /shopping/suppliers/latte-cartelle-drive-thru-coffee-241---245-princes-hwy--ha-1
undefined method `name' for #<Array:0x007faa5302d5a0>
Use SQL joins. When you create a query joining the tables that you are going to use, they will be previously loaded in memory, so the famous n+1 queries will not occur.
#supplier = Supplier.joins(variants: :products).find(params[:id])
# This will translate to something like this
SELECT * FROM suppliers
INNER JOIN variants ON variants.supplier_id = suppliers.id
INNER JOIN products ON products.variant_id = variants.id
WHERE suppliers.id = ?
Remember to always avoid the lazy loading of your associations.

Spree currency convertor is not working for AED

I have included following gem in my gem file
gem 'spree_multi_currency', github: 'spree/spree_multi_currency', branch: '2-3-stable'
in my application following currencies are already present:
SGD,USD,EUR,AUD,GBP,PHP,THB,MYR
and these are converting price properly. But my requirement is to add AED currency to so I have added that also SGD,USD,EUR,AUD,GBP,PHP,THB,MYR,AED from backend
Now I automatically got this option in my header now when i click on AED it gives me following error
Started GET "/assets/world-globe.png" for 127.0.0.1 at 2016-01-26 10:26:08 +0100
Started POST "/currency/set" for 127.0.0.1 at 2016-01-26 10:26:11
+0100 Processing by Spree::CurrencyController#set as JSON Parameters: {"currency"=>"AED"} Spree::Country Load (1.1ms) SELECT "spree_countries".* FROM "spree_countries" WHERE "spree_countries"."name" = 'N/A' LIMIT 1 Spree::User Load (1.2ms) SELECT "spree_users".* FROM "spree_users" WHERE "spree_users"."id" = 1 ORDER BY "spree_users"."id" ASC LIMIT 1 Spree::Order Load (1.2ms) SELECT "spree_orders".* FROM "spree_orders" WHERE "spree_orders"."completed_at" IS NULL AND "spree_orders"."currency" = 'USD' AND "spree_orders"."guest_token" = 'ZtR5IUlQUC40ueZBlo21Pg' AND "spree_orders"."user_id" = 1 LIMIT 1 Spree::Adjustment Load (1.4ms) SELECT "spree_adjustments".* FROM "spree_adjustments" WHERE "spree_adjustments"."adjustable_type" = 'Spree::Order' AND "spree_adjustments"."adjustable_id" IN (6084) ORDER BY spree_adjustments.created_at ASC Spree::Order Load (0.9ms) SELECT "spree_orders".* FROM "spree_orders" WHERE "spree_orders"."user_id" = $1 AND "spree_orders"."completed_at" IS NULL AND (id != 6084) [["user_id", 1]] (0.9ms) BEGIN Spree::Order Exists (1.7ms) SELECT 1 AS one FROM "spree_orders" WHERE ("spree_orders"."number" = 'R501407003' AND "spree_orders"."id" != 6084) LIMIT 1 Spree::LineItem Load (1.3ms) SELECT "spree_line_items".* FROM "spree_line_items" WHERE "spree_line_items"."order_id" = $1 AND (currency != 'AED') ORDER BY created_at ASC [["order_id", 6084]] Spree::Variant Load (1.3ms) SELECT "spree_variants".* FROM "spree_variants" WHERE "spree_variants"."id" = $1 LIMIT 1 [["id", 2]] Spree::Price Load (1.3ms) SELECT "spree_prices".* FROM "spree_prices" WHERE "spree_prices"."deleted_at" IS NULL AND "spree_prices"."variant_id" = $1 AND "spree_prices"."currency" = 'AED' ORDER BY "spree_prices"."id" ASC LIMIT 1 [["variant_id", 2]] Spree::Product Load (1.3ms) SELECT "spree_products".* FROM "spree_products" WHERE "spree_products"."id" = $1 LIMIT 1 [["id", 2]] Spree::Product::Translation Load (1.3ms) SELECT "spree_product_translations".* FROM "spree_product_translations" WHERE "spree_product_translations"."spree_product_id" = $1 [["spree_product_id", 2]] (1.5ms) ROLLBACK Completed 500 Internal Server Error in 56ms
RuntimeError - no AED price found for 28 Day Ultimate Teatox (28 Day): () Users/TopFormInvestment/.rvm/gems/ruby-2.1.4#skinnymint/bundler/gems/spree-cfe7e96539b6/core/app/models/spree/order/currency_updater.rb:34:in `update_line_item_price!' () Users/TopFormInvestment/.rvm/gems/ruby-2.1.4#skinnymint/bundler/gems/spree-cfe7e96539b6/core/app/models/spree/order/currency_updater.rb:18:in `block in update_line_item_currencies!'
Please guide me how to solve this error. As I am new in spree
Go to the Products in admin panel. There you can see the price tab, click the tab and then you change the prices there itself.
You did not set an AED price for the variant 28 Day Ultimate Teatox. In update_line_items_price! it has a local variable price which returns the variants price in the newly set currency. If the price isn't present it will raise the RuntimeError you are getting.
def update_line_item_currencies!
line_items.where('currency != ?', currency).each do |line_item|
update_line_item_price!(line_item)
end
end
# Returns the price object from given item
def price_from_line_item(line_item)
line_item.variant.prices.where(currency: currency).first
end
# Updates price from given line item
def update_line_item_price!(line_item)
price = price_from_line_item(line_item)
if price
line_item.update_attributes!(currency: price.currency, price: price.amount)
else
raise RuntimeError, "no #{currency} price found for #{line_item.product.name} (#{line_item.variant.sku})"
end
end

Rails association skip update associated object

class City<ActiveRecord::Base
has_one :template, class_name:'TmplLocation'
after_initialize :_init
private
def _init
self.template = TmplLocation.find(18) if !self.template
end
end
And that's what happens in console:
>Loc.first.template
City Load (29.8ms) SELECT `locations`.* FROM `locations` WHERE `locations`.`type` IN ('City') LIMIT 1
TmplLocation Load (0.2ms) SELECT `locations`.* FROM `locations` WHERE `locations`.`type` IN ('TmplLocation') AND `locations`.`location_id` = 23 LIMIT 1
TmplLocation Load (34.8ms) SELECT `locations`.* FROM `locations` WHERE `locations`.`type` IN ('TmplLocation') AND `locations`.`id` = ? LIMIT 1 [["id", 18]]
SQL (0.2ms) BEGIN
(0.7ms) UPDATE `locations` SET `location_id` = 23, `updated_at` = '2013-06-11 10:47:11' WHERE `locations`.`type` IN ('TmplLocation') AND `locations`.`id` = 18
(41.4ms) COMMIT
You see? It updates the TmplLocation so now it is constantly associated with this exact city.
I want only use the TmplLocation instance in this City
How to skip update stage??
You can try something like this
class City<ActiveRecord::Base
has_one :template, class_name:'TmplLocation', :conditions => { :id => 18 }
end
For more options see this
guides.rubyonrails.org

rails custom validation not failing when it should

This might not be the cleanest code yet, still quite new to Ruby...
I have the following spam check method in my message.rb model:
validate :no_spam?, :if => "sender_user_id != nil"
private
def no_spam?
#first easy spam detection, if the (hidden by css) company field is filled, it is spam for sure
if !company.blank?
errors.add(:body, I18n.t(:No_spam_allowed))
return false
end
#Mollom advanced spam detection
m = Mollom.new(:private_key => 'xxx',
:public_key => 'xxx')
#check content in case it is a first submit of the form
if captcha_session_id.blank?
content = m.check_content(:post_body => body,
:author_name => sender_name,
:author_mail => sender_email,
:author_ip => sender_ip)
else
#check captcha if the form was resumbitted after an unsure result
logger.debug "DEBUG: GOING TO CHECK CAPTCHA"
result = m.valid_captcha?(:session_id => captcha_session_id,
:solution => captcha_solution.chomp)
if result
logger.debug "DEBUG: TRUE -> GOOD CAPTCHA"
return true
else
logger.debug "DEBUG: FALSE -> BAD CAPTCHA"
return false
end
end
#returning the right values and error messages for different content check outcomes
logger.debug "DEBUG: I'M CONTINUING THE METHOD EXECUTION"
if content.spam?
logger.debug "DEBUG: SPAM DETECTED"
errors.add(:body, I18n.t(:No_spam_allowed))
return false
elsif content.unsure?
logger.debug "DEBUG: MESSAGE UNSURE - FAIL FORM BUT SHOW CAPTCHA"
errors.add(:captcha_solution, I18n.t(:Type_the_characters_you_see_in_the_picture_below))
self.captcha_image_url = m.image_captcha(:session_id => content.session_id)["url"]
self.captcha_session_id = content.session_id
return false
else
logger.debug "DEBUG: MESSAGE OK!"
return true
end
end
development.log
Processing MessagesController#create (for 127.0.0.1 at 2011-08-12 12:01:24) [POST]
Parameters: {"commit"=>"Verzend", "action"=>"create", "authenticity_token"=>"xxxxxxxxxxx", "locale"=>"nl", "controller"=>"messages", "message"=>{"sender_email"=>"[FILTERED]", "company"=>"", "body"=>"unsure", "sender_phone"=>"xxxx", "sender_name"=>"Admin ImmoNatie"}}
Message Columns (6.0ms) SHOW FIELDS FROM `messages`
User Columns (10.0ms) SHOW FIELDS FROM `users`
User Load (7.0ms) SELECT * FROM `users` WHERE (`users`.`id` = '1') AND (users.deleted_at IS NULL ) LIMIT 1
SQL (0.0ms) BEGIN
User Update (0.0ms) UPDATE `users` SET `updated_at` = '2011-08-12 10:01:25', `perishable_token` = 'xxxxxxxxxxx', `last_request_at` = '2011-08-12 10:01:25' WHERE `id` = 1
SQL (3.0ms) COMMIT
SQL (0.0ms) BEGIN
DEBUG: I'M CONTINUING THE METHOD EXECUTION
DEBUG: MESSAGE UNSURE - FAIL FORM BUT SHOW CAPTCHA
SQL (0.0ms) ROLLBACK
Rendering template within layouts/application
Rendering messages/new
Rendered messages/_form (8.0ms)
Rendered layouts/_google_analytics (0.0ms)
Rendered layouts/_login (3.0ms)
Rendered layouts/_navigation (6.0ms)
Rendered layouts/_header (12.0ms)
Rendered about_us/_ten_reasons_9_body (0.0ms)
NewsletterEmail Columns (5.0ms) SHOW FIELDS FROM `newsletter_emails`
Rendered layouts/_footer (41.0ms)
Completed in 1907ms (View: 70, DB: 40) | 200 OK [http://infinitize.dynalias.com/contact]
tize.dynalias.com/contact]
SQL (0.0ms) SET SQL_AUTO_IS_NULL=0
Property Columns (10.0ms) SHOW FIELDS FROM `properties`
Processing MessagesController#create (for 127.0.0.1 at 2011-08-12 12:01:32) [POST]
Parameters: {"commit"=>"Verzend", "action"=>"create", "authenticity_token"=>"xxxxxxxxxxx", "locale"=>"nl", "controller"=>"messages", "message"=>{"sender_email"=>"[FILTERED]", "company"=>"", "body"=>"unsure", "captcha_solution"=>"", "sender_phone"=>"xx", "captcha_session_id"=>"xxxxxxxxxxx", "sender_name"=>"Admin ImmoNatie"}}
Message Columns (5.0ms) SHOW FIELDS FROM `messages`
User Columns (10.0ms) SHOW FIELDS FROM `users`
User Load (0.0ms) SELECT * FROM `users` WHERE (`users`.`id` = '1') AND (users.deleted_at IS NULL ) LIMIT 1
SQL (0.0ms) BEGIN
User Update (0.0ms) UPDATE `users` SET `updated_at` = '2011-08-12 10:01:32', `perishable_token` = 'xxxxxxxxxxx', `last_request_at` = '2011-08-12 10:01:32' WHERE `id` = 1
SQL (4.0ms) COMMIT
SQL (0.0ms) BEGIN
DEBUG: GOING TO CHECK CAPTCHA
DEBUG: FALSE -> BAD CAPTCHA
Message Create (0.0ms) INSERT INTO `messages` (`sender_email`, `receiver_user_id`, `receiver_email`, `created_at`, `body`, `opened_by_owner`, `updated_at`, `receiver_name`, `opened_by_sender`, `sender_ip`, `message_thread_id`, `sender_user_id`, `sender_name`) VALUES('admin#immonatie.be', 1, 'test#immonatie.be', '2011-08-12 10:01:33', 'unsure', NULL, '2011-08-12 10:01:33', 'ImmoNatie', NULL, '127.0.0.1', NULL, 1, 'Admin ImmoNatie')
MessageThread Columns (5.0ms) SHOW FIELDS FROM `message_threads`
MessageThread Create (1.0ms) INSERT INTO `message_threads` (`last_message_opened_by_sender_id`, `answered`, `initial_sender_user_id`, `created_at`, `initial_sender_email`, `starred`, `updated_at`, `last_message_opened_by_owner_id`, `type_id`, `owner_id`, `property_id`, `first_message_id`, `initial_sender_name`, `initial_sender_phone`, `last_message_id`, `last_message_added_at`) VALUES(213, NULL, 1, '2011-08-12 10:01:33', 'admin#immonatie.be', 0, '2011-08-12 10:01:33', 0, 174, 1, NULL, 213, 'Admin ImmoNatie', 'xx', 213, '2011-08-12 10:01:33')
Message Update (0.0ms) UPDATE `messages` SET `updated_at` = '2011-08-12 10:01:33', `message_thread_id` = 101, `sender_name` = 'Admin ImmoNatie', `created_at` = '2011-08-12 10:01:33', `sender_email` = 'admin#immonatie.be', `sender_ip` = '127.0.0.1', `sender_user_id` = 1, `receiver_name` = 'ImmoNatie', `receiver_email` = 'test#immonatie.be', `body` = 'unsure', `receiver_user_id` = 1 WHERE `id` = 213
Sent mail to test#immonatie.be
Date: Fri, 12 Aug 2011 12:01:33 +0200
From: Notifications <no-reply#immonatie.be>
To: test#immonatie.be
Subject: Nieuw contact bericht
Mime-Version: 1.0
Content-Type: text/html; charset=utf-8
Naam: xxxx<br />
E-mail: xxxx<br />
Telefoon: xxxx<br />
<br />
Bericht:<br />
unsure
SQL (3.0ms) COMMIT
In the development.log you see that I first submit the form (first create action) with 'unsure' in the body to invoke an unsure result from Mollom (this site is in development mode).
The validation fails for this first create action as expected.
But when I resubmit the form now with an empty captcha verification code, the validation method returns false, but the validation does not, since the save is continued and COMMITTED in the end.
Why does this validation not fail and break (rollback) the create action?
Thanks,
Michael
Found the problem. Returning false to the validation was not enough. I needed to add an error: errors.add(:body, "error message"). This error together with return false did the job.

Resources