Searching model based on relationships - ruby-on-rails

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)

Related

ActiveRecord::StatementInvalid: Mysql2::Error: Unknown column for `where` query when using includes

I have the following associations:
mobile_application.rb
has_many :events
event.rb
belongs_to :mobile_application
The following runs properly:
MobileApplication.includes(:events)
#=> MobileApplication Load (1.6ms) SELECT `mobile_applications`.* FROM `mobile_applications` ORDER BY created_at desc
# Event Load (1.3ms) SELECT `events`.* FROM `events` WHERE `events`.`mobile_application_id` IN (746, 745, 744, ....
but when I tried with the following,
MobileApplication.includes(:events).where("events.expiry_date >= ?", Time.zone.now)
it throws an error:
MobileApplication Load (1.2ms) SELECT `mobile_applications`.* FROM `mobile_applications` WHERE (events.expiry_date >= '2019-02-18 07:34:40.738517') ORDER BY created_at desc
Mysql2::Error: Unknown column 'events.expiry_date' in 'where clause': SELECT `mobile_applications`.* FROM `mobile_applications` WHERE (events.expiry_date >= '2019-02-18 07:34:40.738517') ORDER BY created_at desc
ActiveRecord::StatementInvalid: Mysql2::Error: Unknown column 'events.expiry_date' in 'where clause': SELECT `mobile_applications`.* FROM `mobile_applications` WHERE (events.expiry_date >= '2019-02-18 07:34:40.738517') ORDER BY created_at desc
Update
Please suggest how I can filter it. Using references also throws the following error & also got same with answer provided by Marek Lipka,
ActiveRecord::StatementInvalid: Mysql2::Error: Column 'created_at' in
order clause is ambiguous: SELECT `mobile_applications`.`id` AS t0_r0,
`mobile_applications`.`name` AS t0_r1, ...
I guess it is due to column ambiguity created by default_scope present in model to order it by created_at which is present in both associated tables.
By default, includes doesn't perform left join, it performs two separate DB queries instead. You can force left join using eager_load, like this:
MobileApplication.eager_load(:events).where('events.expiry_date >= ?', Time.zone.now)
or, if you in fact don't need eager loading (I don't know that), you can simply use joins:
MobileApplication.joins(:events).where('events.expiry_date >= ?', Time.zone.now)
About the error with using references or eager_load: You clearly try to do ordering by created_at somewhere (though you didn't include this in the question) like:
order('created_at DESC')
So, obviously, DB doesn't know what table you have in mind with joins, because there is created_at column both in mobile_applications or events. So you need to specify the 'target' table, like:
order('mobile_applications.created_at DESC')
From rails 4.x, you must add keyword references:
MobileApplication.includes(:events).references(:events).where("events.expiry_date >= ?", Time.zone.now)

How do I count the number of records that have one or more associated object?

I have a Property model that has_many :photos. I want to count the number of properties that have one or more photo.
How do I do that?
I have tried the simple:
> Property.where('properties.photos.count > ?', 0).count
(3.1ms) SELECT COUNT(*) FROM "properties" WHERE (properties.photos.count > 1)
ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR: missing FROM-clause entry for table "photos"
LINE 1: SELECT COUNT(*) FROM "properties" WHERE (properties.photos....
^
: SELECT COUNT(*) FROM "properties" WHERE (properties.photos.count > 0)
from /ruby-2.3.0#myproject/gems/activerecord-3.2.22.5/lib/active_record/connection_adapters/postgresql_adapter.rb:1163:in `async_exec'
Caused by PG::UndefinedTable: ERROR: missing FROM-clause entry for table "photos"
LINE 1: SELECT COUNT(*) FROM "properties" WHERE (properties.photos....
to:
> Property.joins(:photos).where('photos.count > ?', 0).count
(3.7ms) SELECT COUNT(*) FROM "properties" INNER JOIN "photos" ON "photos"."property_id" = "properties"."id" WHERE (photos.count > 0)
ActiveRecord::StatementInvalid: PG::GroupingError: ERROR: aggregate functions are not allowed in WHERE
LINE 1: ..."photos"."property_id" = "properties"."id" WHERE (photos.cou...
^
: SELECT COUNT(*) FROM "properties" INNER JOIN "photos" ON "photos"."property_id" = "properties"."id" WHERE (photos.count > 0)
from ruby-2.3.0#myproject/gems/activerecord-3.2.22.5/lib/active_record/connection_adapters/postgresql_adapter.rb:1163:in `async_exec'
Caused by PG::GroupingError: ERROR: aggregate functions are not allowed in WHERE
LINE 1: ..."photos"."property_id" = "properties"."id" WHERE (photos.cou...
to the more advanced:
>Property.includes(:photos).group(['property.id', 'photos.id']).order('COUNT(photos.id) DESC').count
(0.6ms) SELECT COUNT(DISTINCT "properties"."id") AS count_id, property.id AS property_id, photos.id AS photos_id FROM "properties" LEFT OUTER JOIN "photos" ON "photos"."property_id" = "properties"."id" GROUP BY property.id, photos.id ORDER BY COUNT(photos.id) DESC
ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR: missing FROM-clause entry for table "property"
LINE 1: ...CT COUNT(DISTINCT "properties"."id") AS count_id, property.i...
^
: SELECT COUNT(DISTINCT "properties"."id") AS count_id, property.id AS property_id, photos.id AS photos_id FROM "properties" LEFT OUTER JOIN "photos" ON "photos"."property_id" = "properties"."id" GROUP BY property.id, photos.id ORDER BY COUNT(photos.id) DESC
from ruby-2.3.0#myproject/gems/activerecord-3.2.22.5/lib/active_record/connection_adapters/postgresql_adapter.rb:1163:in `async_exec'
Caused by PG::UndefinedTable: ERROR: missing FROM-clause entry for table "property"
LINE 1: ...CT COUNT(DISTINCT "properties"."id") AS count_id, property.i...
and a few other variations, and they all produce similar errors.
What am I doing wrong?
Note: All I want is the count of properties that have photos.count > 0. I don't want a hash of all the properties and the count of photos. In other words, if there are 5000 properties in my db, I want to build a scope that returns just the properties that actually have photos.
Since all you want is the Propertys with Photos then an INNER JOIN is all you need.
Property.joins(:photos)
That is it. If you want a scope then
class Property < ActiveRecord::Base
scope :with_photos, -> {joins(:photos)}
end
To get the count using rails 3.2
Property.with_photos.count(distinct: true)
You could also use: in rails 3.2
Property.count(joins: :photos, distinct: true)
ActiveRecord::Calculations#count Doc
This will execute
SELECT
COUNT(DISTINCT properties.id)
FROM
properties
INNER JOIN photos ON photos.property_id = properties.id
EDIT:
Property.joins(:photos).group('photos.property_id').having('count(photos.property_id) > 1').count
#=> {1234=>2} # 1234 is property id 2 is count of photos
You will get the property_ids with the number of associated photos with it.
Old Answer:
You can get the properties with atleast one photos associated with it
Property.includes(:photos).where.not(photos: { property_id: nil })
As you are using rails 3.2 .not will not work you have to use
Property.includes(:photos).where("property_id IS NOT null")
Property.includes(:photos).where("SELECT count(photos.id) > 0 FROM photos WHERE property_id = properties.id")
As a scope:
scope :with_photos, -> { where("SELECT count(photos.id) > 0 FROM photos WHERE property_id = properties.id") }
You can try like this, I have done in my projects,
Photo.group(:property_id).count
You will get property id with photos count
results = { 3314=>3, 2033=>3, 3532=>2, 3565=>6, 3510=>1, 3022=>7, 648=>2, 570=>3, 4678=>3, 3540=>1, 3489=>4, 536=>1, 1715=>4 }
Give this a go:
class Property < ApplicationRecord
has_many :photos
def self.with_photos
self.all.reject { |p| p.photos.empty? }
end
end
Property.with_photos.count
Source
More Efficient (Rails 4+):
Property.joins(:photos).uniq.count
Source
More Efficient (Rails 5.1+):
Property.joins(:photos).distinct.count
Source
According to your requirement you can try this
1) A simple count of the number of properties that have 1 or more
photo
To just get the number of properties which have one or more photo you can do this
Property.joins(:photos).distinct.count
As we are not using group the distinct or uniq is necessary. distinct will return ActiveRecord_Relation and uniq will return Array.
2) I would like that set of properties returned so I can
create a scope of just those properties. Also, I do have lots of
properties with more than 1 photo.
To get all the property objects which have one or more than one photo you can use the same query:
Property.joins(:photos).distinct
or you can use the group_by clause:
Property.joins(:photos).group('properties.id')
The difference will be that when you will use size method on the group query it will return a hash with the property_id as key and number of photos on the property as value.
Performance Update:
If you always require the count of associated object and you want to fetch it efficiently you may use counter_cache like this:
class Photo < ApplicationRecord
belongs_to :property, counter_cache: true
end
class Property < ApplicationRecord
has_many :photos
end
And then you will have to add a column in the properties table named photos_count and Rails will automatically update the column each time a new photo record is added or a exisisting photo record is removed. And then you can also query directly:
Property.where('photos_count > ?', 1)
This is optional and you can do this if you are facing performance issues with the data fetch.

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 return the most recent children unique by parent with ActiveRecord

A post has_many comments. A comment belongs_to a post. I'm trying to get back a list of the most recent comments, but only one per post.
The following SQL returns the correct comments results set in PostgreSQL:
SELECT
comments.*
FROM
(SELECT
post_id, MAX(created_at) AS created_at
FROM
comments
GROUP BY
post_id) AS latest_comments
INNER JOIN
comments
ON
comments.post_id = latest_comments.post_id AND
comments.created_at = latest_comments.created_at
How can I define a query to return the same result set with ActiveRecord?
This isn't exactly the equivalent of your SQL, but I believe should also work:
Comment.where(
created_at: Comment.select('max(created_at)').group(:post_id)
)
# Comment Load (6.9ms) SELECT "comments".* FROM "comments" WHERE "comments"."created_at" IN (
# SELECT max(created_at) FROM "comments" GROUP BY "comments"."post_id"
# ) LIMIT $1 [["LIMIT", 11]]

column "users.id" must appear in the GROUP BY clause or be used in an aggregate function

Relationships:
Item belongs to Product
Product belongs to User
Item model scope:
scope :search, ->(search_term) {
select('products.name, users.*, products.brand, COUNT(products.id)')
.joins(:product => :user)
.where('users.name = ? OR products.brand = ?', search_term, search_term)
.group('products.id')
}
The above results in the following SQL statement:
SELECT products.name, users.*, products.brand, COUNT(products.id) FROM "items"
INNER JOIN "products" ON "products"."id" = "items"."product_id"
INNER JOIN "users" ON "users"."id" = "products"."user_id"
WHERE (users.name = 'Atsuete Lipstick' OR products.brand = 'Atsuete Lipstick')
GROUP BY products.id
The problem here is that an error occurs:
ActiveRecord::StatementInvalid: PG::Error: ERROR: column "users.id"
must appear in the GROUP BY clause or be used in an aggregate function
LINE 1: SELECT products.name, users.*, products.brand, COUNT(product...
What could be a fix for this?
From the error you can see that you should try including users.id in the GROUP BY clause:
.group('products.id, users.id')

Resources