ActiveRecord query (includes or joins) - ruby-on-rails

I have two classes:
class Customer
has_many :packages
end
class Package
belongs_to :customer
end
How can I do a query like this?
Customer.includes(:packages).where(packages: 'expires_at < Date.current')
With a sample test from console, I got it: Customer Load (26.0ms) SELECT "customers".* FROM "customers" INNER JOIN "packages" ON "packages"."customer_id" = "customers"."id" WHERE (packages.expires_at < '2019-03-13') LIMIT $1 [["LIMIT", 11]]
Traceback (most recent call last):
ActiveRecord::StatementInvalid (PG::UndefinedFunction: ERROR: operator does not exist: integer = uuid)
LINE 1: ...INNER JOIN "packages" ON "packages"."customer_id" = "custome...
^
HINT: No operator matches the given name and argument type(s). You might need to add explicit type casts.
: SELECT "customers".* FROM "customers" INNER JOIN "packages" ON "packages"."customer_id" = "customers"."id" WHERE (packages.expires_at < '2019-03-13') LIMIT $1
irb(main):003:0>

You can pass the where expression within quotes and bind the value for expires_at.
Customer.includes(:packages).where('packages.expires_at < ?', Date.current)

You can do this:
Customer.includes(:packages).where('packages.expires_at < ?', Date.current).references(:packages)

Customer.joins(:packages).where("packages.expires_at < CURRENT_DATE")
or
Customer.includes(:packages).where("packages.expires_at < CURRENT_DATE").references(:packages)

You can do this:
Customer.includes([:packages]).where(["packages.expires_at < ?', Date.today]).references(:packages).order("Customers.created_at desc")

Related

Unable to order by translated column in Rails using Mobility

This question is based on an issue posted to the Mobility GitHub project.
Context
Rails: 5.0.6
Mobility: 0.4.2 (with table backend)
I'm working with an articles table that supports multiple article types (e.g., blog post, case study, knowledge base article). This table includes a column to track the number of times an article is viewed—an integer column that increments every time a show action is called for an article.
In implementing translations for these articles, I want to track the number of views for each translation individually, not for the main article object. In an effort to achieve this, I included the views property as one of the translated properties on my object:
class Article < ApplicationRecord
include Taggable
include PgSearch
translates :title, type: :string
translates :subtitle, type: :text
translates :body, type: :text
translates :views, type: :integer
multisearchable :against => [:title, :subtitle, :body]
has_and_belongs_to_many :products
attachment :hero_image, content_type: %w(image/jpeg image/png image/gif)
validates :title, :body, :posted_on, presence: true
scope :current, -> { where 'posted_on < ?', Date.tomorrow }
scope :news_articles, -> { where type: ['BlogPost', 'CaseStudy'] }
def log_view(by = 1)
self.views ||= 0
self.views += by
self.save(touch: false)
end
def to_param
"#{id} #{title}".parameterize
end
def published?
posted_on < Date.tomorrow
end
end
Expected Behavior
In my controller, I want to list the top ten most viewed articles, which I get with this query:
#top_articles = Article.current.news_articles.order(views: :desc, posted_on: :desc).limit(10)
I expect to receive an array of articles, as I did before implementing Mobility.
Actual Behavior
What I get instead is #<Article::ActiveRecord_Relation:0x233686c>. If I then try to convert that to an array with #top_articles.to_a, I get this error:
Article Load (0.7ms) SELECT "articles".* FROM "articles" WHERE (posted_on < '2018-02-11') AND "articles"."type" IN ('BlogPost', 'CaseStudy') ORDER BY "articles"."views" DESC, "articles"."posted_on" DESC LIMIT $1 [["LIMIT", 10]]
ActiveRecord::StatementInvalid: PG::UndefinedColumn: ERROR: column articles.views does not exist
LINE 1: ...les"."type" IN ('BlogPost', 'CaseStudy') ORDER BY "articles"...
^
: SELECT "articles".* FROM "articles" WHERE (posted_on < '2018-02-11') AND "articles"."type" IN ('BlogPost', 'CaseStudy') ORDER BY "articles"."views" DESC, "articles"."posted_on" DESC LIMIT $1
from /usr/local/bundle/gems/activerecord-5.0.6/lib/active_record/connection_adapters/postgresql_adapter.rb:598:in `async_exec'
Changing the query to include i18n:
#top_articles = Article.i18n.current.news_articles.order(views: :desc, posted_on: :desc).limit(10)
… returns #<#<Mobility::Backends::ActiveRecord::Table::QueryMethods:0x00000000050a86d8>:0x286c3e0>, and when I try to convert that to an array, I get the same thing:
Article Load (0.7ms) SELECT "articles".* FROM "articles" WHERE (posted_on < '2018-02-11') AND "articles"."type" IN ('BlogPost', 'CaseStudy') ORDER BY "articles"."views" DESC, "articles"."posted_on" DESC LIMIT $1 [["LIMIT", 10]]
ActiveRecord::StatementInvalid: PG::UndefinedColumn: ERROR: column articles.views does not exist
LINE 1: ...les"."type" IN ('BlogPost', 'CaseStudy') ORDER BY "articles"...
^
: SELECT "articles".* FROM "articles" WHERE (posted_on < '2018-02-11') AND "articles"."type" IN ('BlogPost', 'CaseStudy') ORDER BY "articles"."views" DESC, "articles"."posted_on" DESC LIMIT $1
from /usr/local/bundle/gems/activerecord-5.0.6/lib/active_record/connection_adapters/postgresql_adapter.rb:598:in `async_exec'
It turns out that while Mobility supports the use of translated fields in the where clause, it does not currently support them in the order clause of an Active Record query.
Workaround attempts
1. Reference the translation table in the order clause
Based on feedback from the gem author, I tried the query:
Article.i18n.current.news_articles.order('article_translations.views desc', 'articles.posted_on desc')
… which returns a #<#<Mobility::Backends::ActiveRecord::Table::QueryMethods>> object, and to_a returns this error:
ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR: missing FROM-clause entry for table "article_translations"
LINE 1: ...les"."type" IN ('BlogPost', 'CaseStudy') ORDER BY article_tr...
^
: SELECT "articles".* FROM "articles" WHERE (posted_on < '2018-02-12') AND "articles"."type" IN ('BlogPost', 'CaseStudy') ORDER BY article_translations.views desc, articles.posted_on desc LIMIT $1
from /usr/local/bundle/gems/activerecord-5.0.6/lib/active_record/connection_adapters/postgresql_adapter.rb:598:in `async_exec'
2. Add a joins or includes clause for the translation table
Article.i18n.joins(:article_translations).order('article_translations.views desc', 'articles.posted_on desc').limit(10)
This query, again, returns a #<#<Mobility::Backends::ActiveRecord::Table::QueryMethods>> object, and to_a results in:
ActiveRecord::ConfigurationError: Can't join 'Article' to association named 'article_translations'; perhaps you misspelled it?
from /usr/local/bundle/gems/activerecord-5.0.6/lib/active_record/associations/join_dependency.rb:231:in `find_reflection'
3. Add has_many :article_translations to model
Adding a relation to the model throws back this error:
uninitialized constant Article::ArticleTranslation
So…
What should I try next?
UPDATE
Ordering by translated attributes is now supported as of version 0.8.0/
Just do this:
Article.i18n.current.news_articles.
order(:views => :desc, :'articles.posted_on' => :desc)
and Mobility will handle everything (you don't need to join the translation table, etc.)
ORIGINAL ANSWER
You were right that you need to join the translations table, but the association is named translations, not article_translations.
In any case, there is a method join_translations that joins the translation table, so this should work:
Article.i18n.
current.
news_articles.
join_translations.
order('article_translations.views desc', 'articles.posted_on desc')

How to unscope default_scope in join/eager_load?

I have two models:
class User
default_scope -> { where(deleted_at: nil) }
end
class Order
belongs_to :user
end
And I want to get orders with deleted or not deleted users:
Order.joins(:user).merge(User.unscoped)
Order.joins(:user).merge(User.unscope(where: :deleted_at))
# SELECT "orders".* FROM "orders"
# INNER JOIN "users" ON "users"."id" = "orders"."user_id" AND "users"."deleted_at" IS NULL
# ORDER BY "orders"."id" DESC LIMIT 1
Order.eager_load(:user).merge(User.unscoped)
Order.eager_load(:user).merge(User.unscope(where: :deleted_at))
# SELECT "orders"."id" AS t0_r0, "orders"."user_id" AS t0_r1,
# "users"."id" AS t1_r0, "users"."deleted_at" AS t1_r1 FROM "orders"
# LEFT OUTER JOIN "users" ON "users"."id" = "orders"."user_id" AND "users"."deleted_at" IS NULL
# ORDER BY "orders"."id" DESC LIMIT 1
None of these work.
Every query adds "AND "users"."deleted_at" IS NULL" into join statement.
Nothing changes if I specify association scope:
class Order
belongs_to :user, -> { unscoped }
end
However includes works as expected:
Order.includes(:user).merge(User.unscoped).last
# SELECT "orders".* FROM "orders" ORDER BY "orders"."id" DESC LIMIT 1
# SELECT "users".* FROM "users" WHERE "users"."id" = 1054
How can I make rails to unscope association in a join?
You can try like this. It works in Rails >= 3
User.unscoped { Order.joins(:user) }
I solved this issue by writing join query manually. For your case it should look like:
Order.joins('INNER JOIN users ON users.id=orders.user_id')
Although Order.includes(:user).merge(User.unscoped)solution, that you found, looks a bit nicer, unless you really want to have only one query
You can define another association on same model to target specifically those deleted users:
This example works assuming you use act-as-paranoid to handle soft-deletion.
class Order
belongs_to :user
belongs_to :all_user, -> { with_deleted }, foreign_key: 'user_id'
end
Then choose your weapons:
Order.includes(:user).pluck(:email) # Only non soft-deleted emails
Order.includes(:all_user).pluck(:email) # All emails including where deleted_at is null
```

Find method is throwing RecordNotFound when using parent class to search in Single Table Inheritance

When I try to do a find using the parent class, it doesn't work until I use the child class first. This problem exists with Single Table Inheritance. I have my models like this:
class User < ActiveRecord::Base
self.inheritance_column = :meta_type
scope :doctors, -> { where(meta_type: 'Doctor') }
end
class Provider < User
end
class Doctor < Provider
end
When I do Provider.find(1), rails throws record not found. But after I do a successful Doctor.find(1), Provider.find(1) works perfectly fine.
What could be the problem?
EDIT
Here are the SQL queries, notice how rails correctly makes the SQL query to include the Provider in meta_type in the third query:
Provider.find(1):
SELECT "public"."users".* FROM "public"."users" WHERE "public"."users"."meta_type" IN ('Provider') AND "public"."users"."id" = $1 LIMIT 1 [["id", 1]]
Doctor.find(1):
SELECT "public"."users".* FROM "public"."users" WHERE "public"."users"."meta_type" IN ('Doctor') AND "public"."users"."id" = $1 LIMIT 1 [["id", 1]]
Provider.find(1) (second time):
SELECT "public"."users".* FROM "public"."users" WHERE "public"."users"."meta_type" IN ('Provider', 'Doctor') AND "public"."users"."id" = $1 LIMIT 1 [["id", 1]]

Searching model based on relationships

I have a Topic model that belongs_to a Trip. The Trip has a start_date and end_date.
I want to find Topics based on the Trips date. How would I set up this query in rails?
Topic.joins(:trip).where('trip.start_date > ?', Time.now)
this throws the following error.
Topic Load (0.3ms) SELECT "topics".* FROM "topics" INNER JOIN "trips" ON "trips"."id" =
"topics"."trip_id" WHERE (trip.start_date < '2014-10-22 13:17:37.764743') ORDER BY created_at DESC
SQLite3::SQLException: no such column: trip.start_date: SELECT "topics".* FROM "topics" INNER JOIN
"trips" ON "trips"."id" = "topics"."trip_id" WHERE (trip.start_date < '2014-10-22 13:17:37.764743')
ORDER BY created_at DESC
ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: trip.start_date: SELECT
"topics".* FROM "topics" INNER JOIN "trips" ON "trips"."id" = "topics"."trip_id" WHERE
(trip.start_date < '2014-10-22 13:17:37.764743') ORDER BY created_at DESC
How am I structuring this query wrong?
Table names in Rails are by convention plural (contrary to model names) and it's the case here, according to the log. So it should be:
Topic.joins(:trip).where('trips.start_date > ?', Time.now)

ActiveRecord/Postgres: aggregate functions are not allowed in WHERE

I want to retrieve contacts, which have many activities, where the max completed_date of the latter was 6 months ago. Let me ilustrate it:
user = User.first
user.contacts.first.activities.maximum(:completed_date)
# SELECT MAX("activities"."completed_date") AS max_id FROM "activities" WHERE "activities"."user_id" = $1 [["user_id", 12]]
=> 2014-03-18 09:06:54 UTC
Thats perfect. Now I want to use that a clause for a WHERE query but it seems I can't:
user.contacts.joins(:activities)
.where('MAX("activities"."completed_date") < ?', Time.now - 6.months)
# SELECT "contacts".* FROM "contacts"
# INNER JOIN "activities" ON "activities"."contact_id" = "contacts"."id"
# WHERE "contacts"."user_id" = $1 AND (MAX("activities"."completed_date") <= '2013-09-23 05:55:21.191254') [["user_id", 12]]
#=> PG::GroupingError: ERROR: aggregate functions are not allowed in WHERE
# LINE 1: ...ntacts"."id" WHERE "contacts"."user_id" = $1 AND (MAX("activ...
How I'm supposed to do this?
It is complaining because of the MAX, aggregate function, call in the WHERE clause.
To avoid this problem call MAX in SELECT with AS to alias it. Then use the alias in the WHERE.
user.contacts.select('*, MAX("activities"."completed_date") AS max_complete_date').joins(:activities)
.where('max_complete_date < ?', Time.now - 6.months)
Edit
I appologize, you should use HAVING instead.
user.contacts.joins(:activities)
.having('MAX("activities"."completed_date") < ?', Time.now - 6.months).group("contacts.id")

Resources