How to create a model scope with Geocoder? - ruby-on-rails

I have the following models, User, Dad, Mom, Follow and Kid.
class User
has_many :kids
has_many :follows
geocoded_by :address
end
class Dad
has_many :kids
has_many :follows
end
class Mom
has_many :kids
geocoded_by :address
end
class Follow
belongs_to :dad
end
class Kid
belongs_to :mom
belongs_to :dad
belongs_to :user
end
With the Geocoder gem I'm trying to create a scope that takes in the total count of the Dad's Kids nearest to the User's location compared to the Kids Mom's locations that happened today.
<% #follows.each do |f| %>
<% f.dad.kids.today.nearest.count
<% end %>
In my ApplicationController:
def set_user_location
if signed_in?
#user_location = current_user.address
end
end
In my Kid model:
scope :today, -> { where("kids.created_at > ?", Time.now.beginning_of_day) }
scope :nearest, -> { joins(:mom).merge(Mom.near(#user_location, 2000)) }
But the nearest doesn't work. It doesn't load Moms at all.
User Load (0.6ms) SELECT "users".* FROM "users" WHERE "users"."id" = 5 ORDER BY "users"."id" ASC LIMIT 1
Processing by FollowsController#index as HTML
Follow Exists (0.3ms) SELECT 1 AS one FROM "follows" WHERE "follows"."user_id" = $1 LIMIT 1 [["user_id", 5]]
Follow Load (0.5ms) SELECT "follows".* FROM "follows" INNER JOIN "dads" ON "dads"."id" = "follows"."dad_id" WHERE "follows"."user_id" = $1 ORDER BY name ASC LIMIT 30 OFFSET 0 [["user_id", 5]]
Product Load (0.2ms) SELECT "dads".* FROM "dads" WHERE "dads"."id" = $1 ORDER BY "dads"."id" ASC LIMIT 1 [["id", 39]]
(0.7ms) SELECT COUNT(*) FROM "kids" WHERE "kids"."dad_id" = $1 AND (kids.created_at > '2014-05-08 04:00:00.000000') [["dad_id", 39]]
CACHE (0.0ms) SELECT COUNT(*) FROM "kids" WHERE "kids"."dad_id" = $1 AND (kids.created_at > '2014-05-08 04:00:00.000000') [["dad_id", 39]]
What would be the correct way to write the nearest scope?

Problem is with today scope, not nearest.
Every model has sreated_at field so in your scope you should write
scope :today, -> { where("kids.created_at > ?", Time.now.beginning_of_day) }
EDIT:
Another error is that you're referring to controller instance variable in model. Model and controller are separated so it will not work this way. Instead you should rewrite scope to
scope :nearest, ->(location, distance=2000) { joins(:mom).merge(Mom.near(location, distance)) }
and then in the controller you may write
Kid.nearest(some location goes here)
but I think your models are a little messed up and you want them to do something different.
WhatI suppose you want to do is find a mother that is nearest to kid, so rather you should do something like
Mom.near(some_kid, 2000)

Related

Rails 6.0.6 + ApplicationRecord + Problems

It has been three years since I messed with one application of mine. It has always been working, and today, I noticed the following error after run “bundle install”:
File 1 : product.rb
class Product < ApplicationRecord
belongs_to :company
belongs_to :user
belongs_to :sell_unit
belongs_to :currency
has_many :lots
...
File 2 : currency.rb
class Currency < ApplicationRecord
belongs_to :product
...
File 3 : lot.rb
class Lot < ApplicationRecord
belongs_to :product
belongs_to :user
belongs_to :currency
...
def register_new
...
x = self.new {:company_id=>2,
:product_id=>1,
:user_id=>2,
:price=>"10000.0",
:trigger_id=>2,
:label=>"dd1f7a132e59",
"quantity"=>"1",
"expires_at"=>"2022-10-25 19:44:13 +0100",
:currency_id=>1,
:state_id=>5,
:_active=>true,
:activation_pending=>true}
x.save
Calling x.save => It produce the following instructions in order to proceed:
Product Load (0.3ms) SELECT `products`.* FROM `products` WHERE `products`.`id` = NULL LIMIT 1
↳ app/models/lot.rb:54:in `register_new'
User Load (0.2ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = NULL LIMIT 1
↳ app/models/lot.rb:54:in `register_new'
Currency Load (0.2ms) SELECT `currencies`.* FROM `currencies` WHERE `currencies`.`id` = NULL LIMIT 1
↳ app/models/lot.rb:54:in `register_new'
x.erros...
{:product=>["must exist"], :user=>["must exist"], :currency=>["must exist"]}
Why do the intermediate queries assume ID = NULL, if I even pass in the values?
`products`.`id` = NULL LIMIT 1
`users`.`id` = NULL LIMIT 1
`currencies`.`id` = NULL

Rails pre-loading association with multiple foreign keys

Let's say I have the following two models, joined separately by the following two joins models:
class Game
has_many :game_plays
has_many :game_views
end
class Person
has_many :game_plays
has_many :game_views
end
# Games that a Person has played
class GamePlay
belongs_to :game
belongs_to :person
end
# Games that a Person has viewed
class GameView
belongs_to :game
belongs_to :person
end
Given a specific GamePlay, I want to get the GameView for the same Person-Game combo, e.g:
game_play = GamePlay.first
game_view = GameView.find_by(game_id: game_play.game_id, person_id: game_play.person_id)
I also need to eager load that association.
I'd love to create an association between GamePlay and GameView, but nothing I've tried has worked so far.
Attempt 1
class GamePlay
belongs_to :game
belongs_to :person
has_one :game_view, -> (gp) { where(game_id: gp.game_id) }, foreign_key: :person_id, primary_key: :person_id
end
This works, but I can't include this:
GamePlay.includes(:game_view).first
# => ArgumentError: The association scope 'game_view' is instance dependent (the scope block takes an argument). Preloading instance dependent scopes is not supported.
Attempt 2
class GamePlay
belongs_to :game
belongs_to :person
def game_view
GameView.find_by(game_id: game_id, person_id: person_id)
end
end
This obviously works, but I can't include this because it isn't defined as an association.
Any thoughts? Thanks!
Rails 5.0.0postgres 9.6.2
How about:
class GamePlay < ApplicationRecord
belongs_to :game
belongs_to :person
has_one :game_view, through: :person, source: :game_views
end
irb(main):002:0> GamePlay.includes(:game_view).find(2)
GamePlay Load (0.2ms) SELECT "game_plays".* FROM "game_plays" WHERE "game_plays"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]]
Person Load (0.2ms) SELECT "people".* FROM "people" WHERE "people"."id" = 1
GameView Load (0.2ms) SELECT "game_views".* FROM "game_views" WHERE "game_views"."person_id" = 1
=> #<GamePlay id: 2, game_id: 1, person_id: 1>
irb(main):008:0> GamePlay.find(2).game_view
GamePlay Load (0.1ms) SELECT "game_plays".* FROM "game_plays" WHERE "game_plays"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]]
GameView Load (0.2ms) SELECT "game_views".* FROM "game_views" INNER JOIN "people" ON "game_views"."person_id" = "people"."id" WHERE "people"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
=> #<GameView id: 2, game_id: 1, person_id: 1>
I had the same challenge, I solved this with a custom version of Rails preloader https://github.com/2rba/smart_preloader
The association is exactly as you described:
class GamePlay
belongs_to :game
belongs_to :person
has_one :game_view, -> (gp) { where(game_id: gp.game_id) }, foreign_key: :person_id, primary_key: :person_id
end
and then explicitly call preloader as:
game_plays =GamePlay.all
ActiveRecord::SmartPreloader.(game_plays, ActiveRecord::CompositeKey.new(:game_view, [:game_id, :person_id])
that is pretty much the same as Rails default GamePlay.preloads(:game_view) behaviour which under the hood calls ActiveRecord::Associations::Preloader. The only difference preloader called explicitly, and preloader is slightly modified to support multikey and polymorphic associations.

Filter by a field of a join table using JSONAPI Resources

I have three tables:
Observation
Subcategory
Category
with the following structure:
class Observation < ApplicationRecord
translates :name
belongs_to :subcategory
end
class Subcategory < ApplicationRecord
belongs_to :category
has_many :observations
end
class Category < ApplicationRecord
has_many :subcategories
end
I want to be able to filter with JSONAPI::Resource the observations based on a category_id
class ObservationResource < JSONAPI::Resource
attributes :name, :subcategory_id
filter :subcategory_id, :test
filter :test, apply: ->(records, value, _options) {
records.where('subcategories.category_id = ?', value[0])
}
end
If I try to do the request:
/observations?include=subcategory,subcategory.category&filter[subcategory.category_id]=1
I get:
subcategory.category_id is not allowed
If I try to do the request:
/observations?include=subcategory,subcategory.category&filter[test]=1
I get an exception coming from Postgres when trying to execute:
SQL (6.4ms) SELECT DISTINCT "observations"."id", "observations"."id"
AS alias_0 FROM "observations" LEFT OUTER JOIN
"observation_translations" ON
"observation_translations"."observation_id" = "observations"."id"
WHERE (subcategories.category_id = '1') ORDER BY "observations"."id"
ASC LIMIT $1 OFFSET $2 [["LIMIT", 1000], ["OFFSET", 0]]
Because there's no join on the table subcategories
If I add a default scope to include the subcategories all the queries work well but the last one fails, when trying to count the records:
SELECT COUNT(*) FROM "observations" WHERE (subcategories.category_id = '1')
How can I do this properly?
Ok, the solution was to use a ´joins`:
filter :test, apply: ->(records, value, _options) {
records.joins(:subcategory).where('subcategories.category_id = ?', value[0])
}

rails4 has_one association scope with where query

I am developing a Product model that has an has_one association to itself including a raw material record. And the model returns data through REST API. I am using Active Model Serializer instead of JBuilder.
Product model has a 'code' field that contain a product code in string:
'001-000-01-01' (This is a product.)
'001-000-00-01' (This is a material.)
Only difference between two codes is the third number from right. '1' is product. '0' is material. I want to include a "raw_material" record when retrieving a product record. So, I try to set has_one association with scope that has a "where" clause (later I can compose a query to get a material from a product code). Now I simply pass "product" object in lambda and use it in where.
First I write in def raw_material, this works. However, I don't know how to pass an object to def and use it in where clause. Therefore I come up with the scope pattern in has_one, however it returns an error even though it generates exactly the same SELECT as the def pattern. I get "NoMethodError" instead.
class Product < ActiveRecord::Base
has_many :supplies, ->{order('row_order ASC') }, primary_key: :code, foreign_key: :product_code
#This Works!
#has_one :raw_material, class_name: 'Product', primary_key: :code, foreign_key: :code
#This Works!
#has_one :raw_material
#Does not work. Why?
has_one :raw_material, ->(product) { where('code = ?', product.code).take }, class_name: 'Product'
accepts_nested_attributes_for :supplies, allow_destroy: true
#def raw_material
# Product.where('code = ?', '001-000-01-01').take
#end
end
The "def" pattern works:
Started GET "/products/1.json" for ::1 at 2016-10-05 21:48:15 +0900
Processing by ProductsController#show as JSON
Parameters: {"id"=>"1"}
Product Load (0.3ms) SELECT "products".* FROM "products" WHERE "products"."id" = ? LIMIT 1 [["id", 1]]
[active_model_serializers] Supply Load (0.1ms) SELECT "supplies".* FROM "supplies" WHERE "supplies"."product_code" = ? ORDER BY row_order ASC [["product_code", "031-052-00-01"]]
[active_model_serializers] Product Load (0.1ms) SELECT "products".* FROM "products" WHERE (code = '001-000-01-01') LIMIT 1
[active_model_serializers] Rendered ProductSerializer with ActiveModelSerializers::Adapter::Attributes (6.71ms)
Completed 200 OK in 24ms (Views: 9.2ms | ActiveRecord: 1.4ms)
However, the scope pattern does not work:
Started GET "/products/1.json" for ::1 at 2016-10-06 08:19:13 +0900
Processing by ProductsController#show as JSON
Parameters: {"id"=>"1"}
Product Load (0.1ms) SELECT "products".* FROM "products" WHERE "products"."id" = ? LIMIT 1 [["id", 1]]
[active_model_serializers] Supply Load (0.1ms) SELECT "supplies".* FROM "supplies" WHERE "supplies"."product_code" = ? ORDER BY row_order ASC [["product_code", "031-052-00-01"]]
[active_model_serializers] Product Load (0.1ms) SELECT "products".* FROM "products" WHERE (code = '031-052-00-01') LIMIT 1
[active_model_serializers] Rendered ProductSerializer with ActiveModelSerializers::Adapter::Attributes (10.6ms)
Completed 500 Internal Server Error in 27ms (ActiveRecord: 1.2ms)
NoMethodError (undefined method `except' for #<Product:0x007fe97b090418>):
app/controllers/products_controller.rb:17:in `block (2 levels) in show'
app/controllers/products_controller.rb:14:in `show'
Rendered vendor/bundle/ruby/2.2.0/gems/actionpack-4.2.5/lib/action_dispatch/middleware/templates/rescues/_source.erb (8.8ms)
Product Controller simply defines show like this:
# GET /products/1
# GET /products/1.json
def show
respond_to do |format|
format.html
format.json do
render json: #product, include: ['raw_material']
end
end
end
product_serializer.rb is:
class ProductSerializer < ActiveModel::Serializer
attributes(*Product.attribute_names.map(&:to_sym))
has_one :raw_material
end
Any assistance is greatly appreciated.
Thank you
UPDATE:
I solved this issue myself. Please check my answer below. Thank all who wrote solutions.
You can customise the has_one :raw_material as follows.
class ProductSerializer < ActiveModel::Serializer
attributes(*Product.attribute_names.map(&:to_sym))
has_one :raw_material do
Product.where(code: object.code)
end
end
I could solve this myself. Since I don't have to write this in a line, the def(method) pattern is the best bet. How to override "has_many" could help my issue.
Overriding a has_many association getter
"self" can be used for has_one to get an object inside def.
has_one :raw_material
def raw_material
Product.where('code like ?', (self.code)[0,9] + '_' + (self.code)[10,3]).take
end
This works and can generate any dataset you like in a Model.
# Does not work. Why?
has_one :raw_material ->(product) { where('code = ?', product.code).take }...
because you are trying to define a scope here... not an association. if you want a scope, use a scope eg:
scope :raw_material ->(product) { where('code = ?', product.code).first }
if you want an association, then use an association eg
has_one :raw_material, class_name: 'Product', primary_key: :code, foreign_key: :code
don't try to mix the two.

Ruby on Rails ActiveRecord include queries happen while not specifing it

Whenever i try to call this:
#ships = Ship.find(:all,
:conditions => {:sold => params[:sold]},
:order => "ship.id desc")
From this model:
class Ship < ActiveRecord::Base
self.table_name = 'ship'
self.inheritance_column = :ruby_type
belongs_to :brand, :class_name => 'Brand', :foreign_key => :brand
belongs_to :fuel, :class_name => 'Fuel', :foreign_key => :fuel
has_many :ship_pictures, :class_name => 'ShipPicture'
has_many :reservations, :class_name => 'Reservation'
end
I end up with these queries:
Ship Load (0.0ms) SELECT "ship".* FROM "ship" WHERE "ship"."sold" = 'false' ORDER BY ship.id desc
Brand Load (0.0ms) SELECT "brand".* FROM "brand" WHERE "brand"."name" = 'Percedes' LIMIT 1
Fuel Load (0.0ms) SELECT "fuel".* FROM "fuel" WHERE "fuel"."name" = 'Air' LIMIT 1
ShipPicture Load (0.0ms) SELECT "ship_picture".* FROM "ship_picture" WHERE "ship_picture"."ship_id" = 2
Brand Load (1.0ms) SELECT "brand".* FROM "brand" WHERE "brand"."name" = 'Volksship' LIMIT 1
Fuel Load (1.0ms) SELECT "fuel".* FROM "fuel" WHERE "fuel"."name" = 'Nuclear Reactor' LIMIT 1
ShipPicture Load (0.0ms) SELECT "ship_picture".* FROM "ship_picture" WHERE "ship_picture"."ship_id" = 1
Why is this happening? I'm not calling :include or anything like that? I want to join ship_pictures and only get the first result of that join.
And a second thing: What is considered to be better: the symbolic or method way?
This doesn't seem to be lazy loading at play here. If lazy loading was being used I'd expect to only see three queries for Brand, Fuel and ShipPicture with SQL like:
WHERE "ship_picture"."ship_id" IN [1,2]
Do you have any other code in your Ship model that calls any of those associations?
If your finder above is from a controller, you might be calling ship.fuel, ship.brand or ship.ship_pictures somewhere in your view. This is the usual cause of N+1 queries. If it's not from a controller then it is most likely to be some other code that's being run on the results of the query.

Resources