Rails association skip update associated object - ruby-on-rails

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

Related

How can I use a scope in a join query?

I want to use a scope of a joined table.
The goal is to write a scope for autors that have reports with a specific stat_id (for example 15)
Rails 5.2.3
class Author < ApplicationRecord
belongs_to :report
class Report < ApplicationRecord
has_many :authors
scope :with_stat, ->(s) {
where(stat_id: s)
}
This works fine:
Autor.joins(:report).where(reports: {stat_id: 15})
If the scope is more complex. How can I use the scope from class Report?
This doesn't work:
Autor.joins(:report).where(reports: {with_stat(15)})
What is the correct syntax?
That scope will not give you the correct query.
What you want is Author.joins(:report).where(reports: { stat_id: 1 }). Which gives a single query:
Author Load (1.0ms) SELECT "authors".* FROM "authors" INNER JOIN "reports" ON "reports"."id" = "authors"."report_id" WHERE "reports"."stat_id" = $1 LIMIT $2
This is what happens if you use the scope instead:
irb(main):004:0> Author.joins(:report).where(Report.with_stat(1))
Report Load (1.6ms) SELECT "reports".* FROM "reports" WHERE "reports"."stat_id" = $1 [["stat_id", 1]]
Author Load (0.6ms) SELECT "authors".* FROM "authors" INNER JOIN "reports" ON "reports"."id" = "authors"."report_id" LIMIT $1 [["LIMIT", 11]]
=> #<ActiveRecord::Relation []>
irb(main):005:0> Author.joins(:report).where(report: Report.with_stat(1))
Author Load (2.1ms) SELECT "authors".* FROM "authors" INNER JOIN "reports" ON "reports"."id" = "authors"."report_id" WHERE "authors"."report_id" IN (SELECT "reports"."id" FROM "reports" WHERE "reports"."stat_id" = $1) LIMIT $2 [["stat_id", 1], ["LIMIT", 11]]
=> #<ActiveRecord::Relation []>
The later uses a subquery which should give the same result but should be less effective.
What you can do is place the scope on the other side of the association:
class Author < ApplicationRecord
belongs_to :report
scope :with_stat, ->(s){
joins(:report).where(reports: {stat_id: s})
}
end
irb(main):010:0> Author.joins(:report).where(reports: { stat_id: 1 })
Author Load (1.1ms) SELECT "authors".* FROM "authors" INNER JOIN "reports" ON "reports"."id" = "authors"."report_id" WHERE "reports"."stat_id" = $1 LIMIT $2 [["stat_id", 1], ["LIMIT", 11]]
=> #<ActiveRecord::Relation []>

N+1 in has_many :through

I ran into problem N + 1
in association :
class Category < ApplicationRecord
has_many :categories_designs, dependent: :destroy
has_many :designs, through: :categories_designs
has_many :templates, ->{ where(is_template: true) }, through: :categories_designs, class_name: 'Design', source: :design
def marked_designs_as_new?
designs.select(:mark_design_as_new_until).where("mark_design_as_new_until >= ?", Time.now.in_time_zone.beginning_of_day).exists?
end
end
And I want to use the marked_designs_as_new? method in the view.
- #categories.each do |category|
= category.title.titleize
- if category.marked_designs_as_new?
.design-type-marked
NEW
In my controller I call:
#categories = Category.includes(categories_designs: :design).visible
And I'm faced with the problem of N + 1.
Category Load (0.4ms) SELECT "categories".* FROM "categories" WHERE "categories"."hidden" = $1 ORDER BY "categories"."position" ASC LIMIT $2 OFFSET $3 [["hidden", false], ["LIMIT", 100], ["OFFSET", 0]]
CategoriesDesign Load (0.4ms) SELECT "categories_designs".* FROM "categories_designs" WHERE "categories_designs"."category_id" IN (1, 3, 4, 5, 6, 7, 8)
Design Load (0.5ms) SELECT "designs".* FROM "designs" WHERE "designs"."id" IN (1, 4, 3, 6)
(0.7ms) SELECT COUNT(*) FROM "designs" INNER JOIN "categories_designs" ON "designs"."id" = "categories_designs"."design_id" WHERE "categories_designs"."category_id" = $1 AND "designs"."is_template" = $2 [["category_id", 1], ["is_template", true]]
Design Exists (0.7ms) SELECT 1 AS one FROM "designs" INNER JOIN "categories_designs" ON "designs"."id" = "categories_designs"."design_id" WHERE "categories_designs"."category_id" = $1 AND (mark_design_as_new_until >= '2018-03-13 00:00:00') LIMIT $2 [["category_id", 1], ["LIMIT", 1]]
(0.5ms) SELECT COUNT(*) FROM "designs" INNER JOIN "categories_designs" ON "designs"."id" = "categories_designs"."design_id" WHERE "categories_designs"."category_id" = $1 AND "designs"."is_template" = $2 [["category_id", 3], ["is_template", true]]
............. etc.
why?
Ok, your .select(:mark_design_as_new_until) performs another query to the database. What you should do is use an array select method in the following way:
.select(&:mark_design_as_new_until)
This gives you an array of designs loaded in the memory on which you can perform .any? method to check your condition:
.select(&:mark_design_as_new_until).any? { |design| design.mark_design_as_new_until >= Time.now.in_time_zone.beginning_of_day }
And of course, include designs in your Category.
Category.includes(:designs, ...)
Did you try Category.includes([:categories_designs, :design]) Also, you can change the marked_designs_as_new? method as follows,
def marked_designs_as_new?
designs.select{ |x| x.marked_designs_as_new? }.any?
end
design.rb
class Design
def marked_designs_as_new?
mark_design_as_new_until >= Time.now.in_time_zone.beginning_of_day
end
end

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 infinite loop while updating other record's value during `before_save`

I have this model in Rails (trimmed to the relevant parts)
class Session < ActiveRecord::Base
belongs_to :user
before_save :invalidate_existing_sessions
def invalidate_existing_sessions
Session.where(user_id: user.id, current: true).each { |sess| sess.update_attributes(current: false) }
end
end
However, when a record is created and about to be saved, the server goes into an infinite loop.
Here are the server logs
Processing by V1::SessionsController#create as */*
Parameters: {"email"=>"user#example.com", "password"=>"[FILTERED]", "session"=>{}}
User Load (0.7ms) SELECT "users".* FROM "users" WHERE "users"."email" = $1 LIMIT 1 [["email", "user#example.com"]]
(0.2ms) BEGIN
Session Load (0.7ms) SELECT "sessions".* FROM "sessions" WHERE "sessions"."user_id" = $1 AND "sessions"."current" = $2 [["user_id", 1
], ["current", true]]
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", 1]]
CACHE (0.0ms) SELECT "sessions".* FROM "sessions" WHERE "sessions"."user_id" = $1 AND "sessions"."current" = $2 [["user_id", 1], ["cu
rrent", true]]
CACHE (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", 1]]
CACHE (0.0ms) SELECT "sessions".* FROM "sessions" WHERE "sessions"."user_id" = $1 AND "sessions"."current" = $2 [["user_id", 1], ["cu
rrent", true]]
CACHE (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", 1]]
CACHE (0.0ms) SELECT "sessions".* FROM "sessions" WHERE "sessions"."user_id" = $1 AND "sessions"."current" = $2 [["user_id", 1], ["cu
rrent", true]]
A bit later, this is what the log turns into
app/models/session.rb:12:in `invalidate_existing_sessions'
app/models/session.rb:12:in `block in invalidate_existing_sessions'
app/models/session.rb:12:in `invalidate_existing_sessions'
app/models/session.rb:12:in `block in invalidate_existing_sessions'
app/models/session.rb:12:in `invalidate_existing_sessions'
app/models/session.rb:12:in `block in invalidate_existing_sessions'
app/models/session.rb:12:in `invalidate_existing_sessions'
Any ideas? I'm using Rails 5 alpha.
It's because your before_save method does this...
sess.update_attributes(current: false)
Since update_attributes calls before_save you are (as you say) in an infinite loop.
So you need to skip the callbacks
class Session < ActiveRecord::Base
attr_accessor :skip_callbacks
before_save :invalidate_existing_sessions, unless: :skip_callbacks
def invalidate_existing_sessions
Session.where(user_id: user.id, current: true).each do |sess|
sess.skip_callbacks = true
sess.update_attributes(current: false)
end
end
Even though all of the above answers worked for me, this is what I found simplest and I ended up using.
def invalidate_existing_sessions
Session.where(user_id: user.id, current: true).each { |sess| sess.update_column(:current, false) }
end
Turns out update_column doesn't call any callbacks, but as an disadvantage it doesn't update updated_at if you're using timestamps in your model.
You're running update_attributes in before_save, that means you're saving before save. That's why it goes into an infinite loop.

How to compare Rails enum types

I have the following enum model in my Rails (4) application:
class Dual < ActiveRecord::Base
enum dual: [:dual, :not_dual]
validates :dual, uniqueness: true
validates :dual, presence: true
end
And I have another model which has many Duals:
class SillColour < ActiveRecord::Base
has_many :sill_colour_duals, dependent: :destroy
has_many :duals, through: :sill_colour_duals
end
I want to be able to test if an instance of SillColour has a Dual enum. This is all I could get to work:
dual = Dual.find(1)
not_dual = Dual.find(2)
sill_colour.duals.include?(dual)
sill_colour.duals.include?(not_dual)
Obviously this is extremely unreliable as the ID of the Duals could be anything in production (for testing IDs are fixed). I tried this:
dual = Dual.where(dual: 0)
not_dual = Dual.where(dual: 1)
and even given the database duals table looks like this:
id | dual
----+------
1 | 0
2 | 1
My tests fail and it seems to be because dual and non_dual are no longer comparing correctly. I've examined them using pry and they appear to be the same as before, but clearly they're not.
Surely there must be a better way? I envisaged being able to do this:
sill_colour.duals.include?(Dual.dual)
sill_colour.duals.include?(Dual.not_dual)
but this doesn't work either.
Any suggestions?
I'll try to answer with a code from my app
class User < ActiveRecord::Base
has_many :contacts
end
class Contact < ActiveRecord::Base
enum status: [:dual, :not_dual]
# see scopes
scope :dual, -> {where(status: Contact.statuses['dual']) }
scope :not_dual, -> {where(status: Contact.statuses['not_dual']) }
end
Now console, check if a User instance has a contact with dual status:
2.1.5 :001 > u = User.last
User Load (0.8ms) SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT 1
=> #<User id: 1, email: "...", ...>
2.1.5 :003 > u.contacts.dual
Contact Load (0.3ms) SELECT "contacts".* FROM "contacts" WHERE "contacts"."user_id" = $1 AND "contacts"."status" = 0 [["user_id", 1]]
=> #<ActiveRecord::AssociationRelation []>
2.1.5 :004 > u.contacts.not_dual
Contact Load (0.3ms) SELECT "contacts".* FROM "contacts" WHERE "contacts"."user_id" = $1 AND "contacts"."status" = 1 [["user_id", 1]]
=> #<ActiveRecord::AssociationRelation []>
As you can see both return empty array, so calling any? on empty array will return false both. Because I don't have any contact with status dual or not_dual, let's create one.
Find a contact:
2.1.5 :005 > c = Contact.last
Contact Load (0.5ms) SELECT "contacts".* FROM "contacts" ORDER BY "contacts"."id" DESC LIMIT 1
=> #<Contact id: 3, user_id: 1, ..., ...., status: nil>
Set it as dual:
2.1.5 :006 > c.dual!
(0.1ms) BEGIN
SQL (15.1ms) UPDATE "contacts" SET "status" = $1, "updated_at" = $2 WHERE "contacts"."id" = 3 [["status", 0], ["updated_at", "2014-12-29 13:16:45.576778"]]
(25.5ms) COMMIT
=> true
Now check if user has dual or not_dual contacts:
2.1.5 :009 > u.contacts.dual.any?
(0.2ms) SELECT COUNT(*) FROM "contacts" WHERE "contacts"."user_id" = $1 AND "contacts"."status" = 0 [["user_id", 1]]
=> true
2.1.5 :010 > u.contacts.not_dual.any?
(0.3ms) SELECT COUNT(*) FROM "contacts" WHERE "contacts"."user_id" = $1 AND "contacts"."status" = 1 [["user_id", 1]]
=> false
2.1.5 :011 >
Checking if user instance has a dual enum returns true. In your case instead of user it will be sill_colour.
If you don't like scopes you can use where:
u.contacts.where(status: Contact.statuses['dual']).any?
=> true
http://edgeapi.rubyonrails.org/classes/ActiveRecord/Enum.html
class Dual < ActiveRecord::Base
enum status: [:dual, :not_dual]
end
Having the class as the one above you can check the status like this:
dual = Dual.find(params[:id])
dual.dual? # will return true or false depends on the status you set.
dual.not_dual? # same, true or false
dual.status = "dual" # if status was set to 0 or not_dual if status was set to 1

Resources