Rails query subclasses and limit one each - ruby-on-rails

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.

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/

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.

ActiveRecord has_many relationships forget changes to children?

I have an instance of type A that has_many Bs. When the A.foo = value method gets called, I actually want to write a method that delegates to that foo= call the first of the A's Bs.
class A < ActiveRecord::Base
has_many :bs, autosave: true
def foo
bs.first.foo
end
def foo=(val)
bs.first.foo = val
end
end
class B < ActiveRecord::Base
belongs_to A
end
rails generate model A
rails generate model B a:references foo:string
2.3.0 :001 > a = A.create!
(0.1ms) begin transaction
SQL (0.3ms) INSERT INTO "as" ("created_at", "updated_at") VALUES (?, ?) [["created_at", "2016-10-08 18:03:18.255107"], ["updated_at", "2016-10-08 18:03:18.255107"]]
(7.8ms) commit transaction
=> #<A id: 1, created_at: "2016-10-08 18:03:18", updated_at: "2016-10-08 18:03:18">
Create an A and call it a.
2.3.0 :002 > b = B.create!(a: a, foo: "initial")
(0.4ms) begin transaction
SQL (0.4ms) INSERT INTO "bs" ("a_id", "foo", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["a_id", 1], ["foo", "initial"], ["created_at", "2016-10-08 18:03:40.658035"], ["updated_at", "2016-10-08 18:03:40.658035"]]
(8.3ms) commit transaction
=> #<B id: 1, a_id: 1, foo: "initial", created_at: "2016-10-08 18:03:40", updated_at: "2016-10-08 18:03:40">
Create a B and call it b. Make it a child of A. Set it's foo property to "initial".
2.3.0 :003 > a.reload.foo
A Load (0.2ms) SELECT "as".* FROM "as" WHERE "as"."id" = ? LIMIT 1 [["id", 1]]
B Load (0.2ms) SELECT "bs".* FROM "bs" WHERE "bs"."a_id" = ? ORDER BY "bs"."id" ASC LIMIT 1 [["a_id", 1]]
=> "initial"
Check that a sees it's new child's foo: yes. As expected.
2.3.0 :004 > a.foo = "set"
B Load (0.1ms) SELECT "bs".* FROM "bs" WHERE "bs"."a_id" = ? ORDER BY "bs"."id" ASC LIMIT 1 [["a_id", 1]]
=> "set"
2.3.0 :005 > a.foo
B Load (0.4ms) SELECT "bs".* FROM "bs" WHERE "bs"."a_id" = ? ORDER BY "bs"."id" ASC LIMIT 1 [["a_id", 1]]
=> "initial"
Whaaat? I just called a.foo = "set". Now when I call a.foo again to read the value back, I get "initial"? That's not the way it works for has_one relationships. Why is ActiveRecord reloading from the DB every time, instead of caching it's queries?
Ultimately, my intention is to call a.save!, and have it autosave down to the b. But that's not possible if the relationship gets amnesia about every pending change. What's going on here?!
Set up a has_one relationship between A and B and delegate :foo to the has_one association.
class A
has_many :bs
has_one :first_b, -> { first },
class_name: 'B'
delegate :foo, to: :first_b
end
To avoid the query for B you can use .joins, includes or eager_load.

has_many/belongs_to relationships & query questions

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".

ActiveRecord::Base doesn't belong in a hierarchy descending from ActiveRecord

I'm trying to create a Rails plugin. For the most part, what I've written works. However, there's a problem with associations. When I try to call an association, I get this error:
ActiveRecord::Base doesn't belong in a hierarchy descending from ActiveRecord
At the moment, the plugin looks like this:
module ControlledVersioning
module ActsAsVersionable
extend ActiveSupport::Concern
included do
has_many :versions, as: :versionable
after_create :create_initial_version
end
module ClassMethods
def acts_as_versionable(options = {})
cattr_accessor :versionable_attributes
self.versionable_attributes = options[:versionable_attributes]
end
end
private
def create_initial_version
version = versions.create
end
end
end
ActiveRecord::Base.send :include, ControlledVersioning::ActsAsVersionable
Again, the error message is triggered whenever I try to call the association. I used debugger in the after_create callback and tried running:
> versions.create
*** ActiveRecord::Base doesn't belong in a hierarchy descending from ActiveRecord
> versions
*** ActiveRecord::Base doesn't belong in a hierarchy descending from ActiveRecord
> Version.new
#<Version id: nil, versionable_id: nil, versionable_type: nil>
There are a few things you need to change in your code in order for it to work.
First, versions is a reserved keyboard from rails -- you can't have a relationship with that name - (I used the name versionings in order to make it work)
Also, you want to make sure to just add has_many versionings for the models that want to acts_as_versionable - meaning, move has_many :versionings, as: :versionable, class_name: 'Version' and after_create :create_initial_version calls to inside the acts_as_versionable method.
Here's how all together will look like:
module ControlledVersioning
module ActsAsVersionable
extend ActiveSupport::Concern
module ClassMethods
def acts_as_versionable(options = {})
has_many :versionings, as: :versionable, class_name: 'Version'
after_create :create_initial_version
cattr_accessor :versionable_attributes
self.versionable_attributes = options[:versionable_attributes]
end
end
private
def create_initial_version
version = versionings.create
end
end
end
ActiveRecord::Base.send :include, ControlledVersioning::ActsAsVersionable
Doing those changes made the plugin work for me:
irb(main):003:0> Post.create!
(0.1ms) begin transaction
Post Create (0.7ms) INSERT INTO "posts" ("created_at", "updated_at") VALUES (?, ?) [["created_at", "2019-07-16 08:55:13.768196"], ["updated_at", "2019-07-16 08:55:13.768196"]]
Version Create (0.2ms) INSERT INTO "versions" ("versionable_type", "versionable_id", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["versionable_type", "Post"], ["versionable_id", 3], ["created_at", "2019-07-16 08:55:13.772246"], ["updated_at", "2019-07-16 08:55:13.772246"]]
(2.0ms) commit transaction
=> #<Post id: 3, created_at: "2019-07-16 08:55:13", updated_at: "2019-07-16 08:55:13", name: nil>
irb(main):004:0> Post.last.versionings
Post Load (0.2ms) SELECT "posts".* FROM "posts" ORDER BY "posts"."id" DESC LIMIT ? [["LIMIT", 1]]
Version Load (0.2ms) SELECT "versions".* FROM "versions" WHERE "versions"."versionable_id" = ? AND "versions"."versionable_type" = ? LIMIT ? [["versionable_id", 3], ["versionable_type", "Post"], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Version id: 2, versionable_type: "Post", versionable_id: 3, created_at: "2019-07-16 08:55:13", updated_at: "2019-07-16 08:55:13">]>
irb(main):005:0>
I would try extending active record in an initializer instead of including it.
initializers/acts_as_versionable.rb
ActiveRecord::Base.extend(ControlledVersioning::ActsAsVersionable)
Also in development; or any environment that reloads the files you'll likely see an error like has been removed from the module tree but is still active. Make sure you're plugin file is in config.eager_load_paths and not actually in a concern path.

Resources