has_many/belongs_to relationships & query questions - ruby-on-rails

I am building an attendance tracker right now. I have three models currently. I am using devise for my User model
class User < ActiveRecord::Base
has_many :clients
has_many :attendances
class Client < ActiveRecord::Base
belongs_to :user
has_many :attendances
class Attendance < ActiveRecord::Base
belongs_to :user
belongs_to :client
The columns on the attendance table are user_id client_id created_at and updated_at
Here's my thought process:
I can get all of the attendances for each User
I can also get all of the attendances for a specific Client
I can get all of the attendance records for a specific User and Client by way of: Attendance.joins(:user, :client).where(:user_id => current_user) which returns an
<ActiveRecord::Relation [#<Attendance id: 18, client_id: 151, created_at: "2015-07-24 21:36:16", updated_at: "2015-07-24 21:36:16", user_id: 4>, #<Attendance id: 19, client_id: 101, created_at: "2015-07-24 21:37:10", updated_at: "2015-07-24 21:37:10", user_id: 4>, #<Attendance id: 20, client_id: 114, created_at: "2015-07-24 21:37:39", updated_at: "2015-07-24 21:37:39", user_id: 4>, #<Attendance id: 21, client_id: 123, created_at: "2015-07-24 21:38:26", updated_at: "2015-07-24 21:38:26", user_id: 4>]>
Can I somehow refer back to Client table to get information like first_name or email with another where, include, or joins statement?
Or am I missing something altogether and maybe need a join table and do a has_many, through: relationship?

Brad's comment above is technically correct, but since the .joins clause on your query only includes those tables for the execution of the actual query, the data from the Users and Clients tables isn't actually going to get loaded in by Rails, so in order to fetch all their data you'll end up executing N+1 queries (a common cause of slow Rails apps!). That is:
irb> User.joins(:groups).each { |u| u.groups.map(&:name) }
User Load (8.6ms) SELECT "users".* FROM "users" INNER JOIN "groups" ON "groups"."user_id" = "users"."id"
Group Load (1.2ms) SELECT "groups".* FROM "groups" WHERE "groups"."user_id" = $1 [["user_id", 1]]
Group Load (0.6ms) SELECT "groups".* FROM "groups" WHERE "groups"."user_id" = $1 [["user_id", 1]]
Group Load (0.7ms) SELECT "groups".* FROM "groups" WHERE "groups"."user_id" = $1 [["user_id", 1]]
Group Load (0.7ms) SELECT "groups".* FROM "groups" WHERE "groups"."user_id" = $1 [["user_id", 2]]
Group Load (0.6ms) SELECT "groups".* FROM "groups" WHERE "groups"."user_id" = $1 [["user_id", 3]]
Not so bad now, but imagine if you had a thousand users! We can fix this, though. If you use the .includes method, you'll both join onto the other table(s) and load their data into memory. It still runs 2 queries, but that's an improvement:
irb(main):016:0> User.includes(:groups).each { |u| u.groups.map(&:name) }
User Load (0.6ms) SELECT "users".* FROM "users"
Group Load (1.2ms) SELECT "groups".* FROM "groups" WHERE "groups"."user_id" IN (1, 2, 3, 4)
So, for your case, try this instead:
Attendance.includes(:user, :client).where(user_id: current_user)
For more info on the difference between includes and joins, see this blog post, or Google "rails joins vs includes".

Related

What the difference between implicit_order_column and default_scope in Rails?

What the difference between:
self.implicit_order_column = 'id'
and
default_scope { order('id ASC') }
self.implicit_order_column lets you use another column then the primary key as the implicit ordering column. This effects the way that methods like .first and .last work:
User.class_eval do
self.implicit_order_column = 'created_at'
end
User.first
# => SELECT "users".* FROM "users" ORDER BY "users"."updated_at" ASC LIMIT $1 [["LIMIT", 1]]
User.last
# => SELECT "users".* FROM "users" ORDER BY "users"."updated_at" DESC LIMIT $1 [["LIMIT", 1]]
Setting self.implicit_order_column = 'id' is utterly pointless since the default is the primary key column anyways. The implicit_order_column is of course not used if you provide an explicit order. It does not actually change any other scopes spawned off the class.
default_scope on the other hand tacks on a default scope to any scopes that you spawn off the class.
irb(main):001:0> User.all
(0.5ms) SELECT sqlite_version(*)
User Load (0.1ms) SELECT "users".* FROM "users" LIMIT ? [["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<User id: 1, role: "admin", created_at: "2020-11-08 19:31:31", updated_at: "2020-11-08 19:31:47">]>
irb(main):002:1* User.class_eval do
irb(main):003:1* default_scope { order(id: :asc) }
irb(main):004:0> end
=> [#<Proc:0x00000000043703a8 (irb):3>]
irb(main):005:0> User.all
User Load (0.2ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<User id: 1, role: "admin", created_at: "2020-11-08 19:31:31", updated_at: "2020-11-08 19:31:47">]>
irb(main):006:0>
The difference here is not immediately apparent. But if you consider that in the SQL world a query with no order clause will not return the records in a determinate order (its implementation dependent) and here we are actually getting the records in a determinate order. Om many RDBMS the results will be hard to destinguish as they might return the records in the order they are modified (if they feel like it).
Seems brilliant until you realize how non-intuitive default_scope is and how many bugs it leads to down the line.
irb(main):006:0> User.all.order(:created_at)
User Load (0.2ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC, "users"."created_at" ASC LIMIT ? [["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<User id: 1, role: "admin", created_at: "2020-11-08 19:31:31", updated_at: "2020-11-08 19:31:47">]>
irb(main):007:0>
Or this example:
irb(main):001:1* User.class_eval do
irb(main):002:1* default_scope { where(admin: true) }
irb(main):003:0> end
=> [#<Proc:0x0000000002bde460 (irb):2>]
irb(main):004:0> User.new
(0.6ms) SELECT sqlite_version(*)
=> #<User id: nil, role: "visitor", created_at: nil, updated_at: nil, admin: true>
Yikes! default_scope is thus widely considered evil.
See:
https://github.com/rails/rails/pull/34480
https://rails-bestpractices.com/posts/2013/06/15/default_scope-is-evil/
https://www.reddit.com/r/rails/comments/ha6gqj/default_scope_is_evil/

PaperTrail with STI appears to be storing wrong item_type

According to the docs (and specs) this STI setup below should work to log the history under my Agency subclass:
class Entity < ApplicationRecord
end
class Agency < Entity
has_paper_trail
end
however, the item_type on the history is being stored as Entity and the version history is not working as expected
pry> Agency.create name:'My Org'
=> #<Agency:0x00007fab7b003740
id: 103,
type: "Agency",
name: "My Org"
...
pry> Agency.last.version
Agency Load (0.4ms) SELECT "entities".* FROM "entities" WHERE "entities"."type" = $1 ORDER BY "entities"."id" DESC LIMIT $2 [["type", "Agency"], ["LIMIT", 1]]
=> nil
pry> Agency.last.versions
PaperTrail::Version Load (0.4ms) SELECT "versions".* FROM "versions" WHERE "versions"."item_id" = $1 AND "versions"."item_type" = $2 ORDER BY "versions"."created_at" ASC, "versions"."id" ASC [["item_id", 103], ["item_type", "Entity"]]
=> [#<PaperTrail::Version:0x00007fab72ec2118
id: 222,
item_type: "Entity",
item_id: 103,
event: "create",
whodunnit: nil,
object: nil,
created_at: Mon, 29 Jun 2020 15:59:24 EDT -04:00,
object_changes:
{"id"=>[nil, 103],
"name"=>[nil, "My Org"],
"slug"=>[nil, "my-org"],
"type"=>[nil, "Agency"],
"created_at"=>[nil, "2020-06-29T15:59:24.344-04:00"],
"updated_at"=>[nil, "2020-06-29T15:59:24.344-04:00"]}>]
Am I missing something or might this be a bug?
Rails 6.0.3.2, PaperTrail 10.3.1
This bug is mentioned in issue #594, and if you establish an item_subtype column in your versions table then it provides a workaround.
If you instead want item_type to always refer to the subclass name -- for instance, "Agency" in your example -- then a fix is available by referencing this version of PaperTrail:
gem 'paper_trail', github: 'paper-trail-gem/paper_trail', ref: '91deff4324e079cc2af044d5b9471c5ceaa50191'
Reason this works is that this issue was fixed in mid-2018 in PR 1108. A month after it was committed though, the fix was reverted. We've carried on using this older version of PaperTrail since then in a large-scale application, and it's worked out well.

ActiveRecord::StatementInvalid: PG::UndefinedTable in many to many relation but table exists

I have an easy many to many relation and It doesn't work and I cannot understand why. I'm sure that is something so obvious... but..
class Content < ApplicationRecord
has_many :content_brands
has_many :brands, through: :content_brands
end
class ContentBrand < ApplicationRecord
belongs_to :content
belongs_to :brand
end
class Brand < ApplicationRecord
establish_connection Rails.application.config.brands_database_configuration
has_many :content_brands
has_many :contents, through: :content_brands
end
But
irb(main):002:0> Content.first.brands
ActiveRecord::StatementInvalid: PG::UndefinedTable: ERRORE: la relazione "content_brands" non esiste
LINE 1: SELECT "brands".* FROM "brands" INNER JOIN "content_brands"...
^
: SELECT "brands".* FROM "brands" INNER JOIN "content_brands" ON "brands"."id" = "content_brands"."brand_id" WHERE "content_brands"."content_id" = $1 ORDER BY "brands"."name" ASC LIMIT $2
The table exists, I can query it
irb(main):006:0> ContentBrand.first.brand
ContentBrand Load (0.5ms) SELECT "content_brands".* FROM "content_brands" ORDER BY "content_brands"."id" ASC LIMIT $1 [["LIMIT", 1]]
Brand Load (27.4ms) SELECT "brands".* FROM "brands" WHERE "brands"."id" = $1 ORDER BY "brands"."name" ASC LIMIT $2 [["id", 1], ["LIMIT", 1]]
=> #<Brand id: 1, name: "Nokia", logo: "nokia.jpeg", created_at: "2016-12-08 15:50:48", updated_at: "2017-02-02 15:51:43", web_site: "http://www.nokia.it">
Why?
I'm getting crazy because the inverse relation works
Brand.first.contents
Brand Load (25.8ms) SELECT "brands".* FROM "brands" ORDER BY "brands"."name" ASC LIMIT $1 [["LIMIT", 1]]
Content Load (0.7ms) SELECT "contents".* FROM "contents" INNER JOIN "content_brands" ON "contents"."id" = "content_brands"."content_id" WHERE "content_brands"."brand_id" = $1 ORDER BY "contents"."published_at" DESC LIMIT $2 [["brand_id", 391], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy []>
irb(main):011:0>
Update: I forgot to tell you that Brand is on another database...
You can't setup associations to a model that is stored in another database in ActiveRecord. Which makes sense since you can't join another database in a single query in Postgres without jumping through some pretty serious hoops (Postgres_FDW). And with the polyglot nature of ActiveRecord this would just be too much complexity for a very limited use case.
If its in any way possible I would switch to a single database setup even if it means that you have to duplicate data.
If you look at the "inverse query" you can see that it works because its not a single query:
# queries the "brands" database
Brand Load (25.8ms) SELECT "brands".* FROM "brands" ORDER BY "brands"."name" ASC LIMIT $1 [["LIMIT", 1]]
# queries your main database
Content Load (0.7ms) SELECT "contents".* FROM "contents" INNER JOIN "content_brands" ON "contents"."id" = "content_brands"."content_id" WHERE "content_brands"."brand_id" = $1 ORDER BY "contents"."published_at" DESC LIMIT $2 [["brand_id", 391], ["LIMIT", 11]]
However this does not mean that the concept is feasible.

Rails query subclasses and limit one each

I have an artifacts table that holds downloaded files. Each type holds different data.
class Artifact < ActiveRecord::Base
end
class ForecastArtifact < Artifact
end
class ChargebackArtifact < Artifact
end
class CatalogArtifact < Artifact
end
For a single account, I am querying the MOST RECENT of each artifact type:
artifact_types = Artifact.subclasses.map(&:to_s)
artifacts_for_account = artifact_types.map do |type|
#account.artifacts
.where(type: type)
.order("valid_as_of DESC")
.limit(1).first
.as_json({only: [:id, :account_id, :type]})
end
unfortunately this does N queries.
Is there a single query you would do for this? a more rails-style of solving this problem?
If you're using PostgreSQL, you can use DISTINCT ON to solve this:
>> a = Account.last
Account Load (0.4ms) SELECT "accounts".* FROM "accounts" ORDER BY "accounts"."id" DESC LIMIT $1 [["LIMIT", 1]]
=> #<Account id: 1, created_at: "2017-11-17 17:16:03", updated_at: "2017-11-17 17:16:03">
>> a.artifacts.select("distinct on (type) valid_as_of, type, id").group(:type, :valid_as_of, :id).order(:type, valid_as_of: :desc)
Artifact Load (0.6ms) SELECT distinct on (type) valid_as_of, type, id FROM "artifacts" WHERE "artifacts"."account_id" = $1 GROUP BY "artifacts"."type", "artifacts"."valid_as_of", "artifacts"."id" ORDER BY "artifacts"."type" ASC, "artifacts"."valid_as_of" DESC LIMIT $2 [["account_id", 1], ["LIMIT", 11]]
=> #<ActiveRecord::AssociationRelation [#<ChargebackArtifact id: 22, type: "ChargebackArtifact", valid_as_of: "2017-11-17 17:06:03">, #<ForecastArtifact id: 21, type: "ForecastArtifact", valid_as_of: "2017-11-17 17:11:03">]>
This requires only one query for each account.

ActiveRecord::Querying#first returns third member of the collection

[1] pry(main)> User.first
User Load (0.4ms) SELECT "users".* FROM "users" LIMIT 1
=> #<User id: 3, email: "chasity#kiehn.net", encrypted_password: "$2a$10$lqsgKvQuz9iSIu/..FMRJu76H9KNhfVz5x9DmxphC0TK...", reset_password_token: ... >
[2] pry(main)> User.find(1)
User Load (12.8ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", 1]]
=> #<User id: 1, email: "admin#example.com", encrypted_password: "$2a$10$pGDcv0/EgiDk4KcZN9pli.evx5Ni1qOoujQD15HgWH8Y...", reset_password_token: ... >
[3] pry(main)> Product.first
Product Load (0.6ms) SELECT "products".* FROM "products" LIMIT 1
=> #<Product id: 1, name: "Ruby on Rails Jr. Spaghetti", created_at: "2012-01-25 10:13:26", updated_at: "2012-01-25 10:13:26", properties: ...>
This is happening in the console and during runtime and only for the User model. What could cause such behaviour?
You haven't specified an order - the database is free to order the results in any way it wants. It could be primary key order, it could be how things are laid out on disc but without an order clause there are no guarantees.
If you want the first record in a specific order, then make sure you ask for it
User.first is the same as User.all.first, there is no specified order, so the DB returns the list of element in any order. If you want to get the User with smallest id, use User.order("id").first.
If your request returns several elements and you want to pick any of them, you may want to use first without specified order.

Resources