Condition on join tables - Rails - ruby-on-rails

My models:
class Review < ActiveRecord::Base
belongs_to :business
class Business < ActiveRecord::Base
has_many :reviews
has_and_belongs_to_many :categories
I want to get the Reviews for businesses under a certain category:
Review.joins(:business => :categories).where(:business => {:categories => [1,2,3,4]})
The resulting query:
SELECT "reviews".* FROM "reviews" INNER JOIN
"businesses" ON "businesses"."id" = "reviews"."business_id" INNER JOIN
"businesses_categories" ON "businesses_categories"."business_id" = "businesses"."id"
INNER JOIN "categories" ON "categories"."id" = "businesses_categories"."category_id"
WHERE "business"."categories" IN (1, 2, 3, 4)
However, I am getting the following error:
ActiveRecord::StatementInvalid: PG::Error: ERROR: missing FROM-clause entry
for table "business"
LINE 1: ...id" = "businesses_categories"."category_id" WHERE "business"...

Use this:
Review.joins(:business => :categories).where( :categories => { :id => [1,2,3,4] } )

Related

How to search group_by with association in Rails

I don't know how I describe this question, at first I want show my model which maintaining a relation like below
category.rb
class Category < ApplicationRecord
has_many :job_categories, dependent: :destroy
has_many :jobs, through: :job_categories
end
job.rb
class Job < ApplicationRecord
has_many :job_categories, dependent: :destroy
has_many :categories, through: :job_categories
end
job_category.rb
class JobCategory < ApplicationRecord
belongs_to :category, counter_cache: :jobs_count
belongs_to :job
end
schema.rb
create_table "categories", force: :cascade do |t|
t.string "name"
t.string "parent"
end
the parent is a column which maintain the group like Technology and under this ruby,rails,programming etc which is Technology related.
Below is my query for showing group by category
Category.select(:id, :name, :parent).group_by{|p| p.parent}
and it's showing like this
Technology
ruby
rails
etc
Now I want to show all jobs in group by Technology, I have a query for this like
Job.joins(:categories).where('lower(categories.parent) LIKE lower(?)', "%#{params[:parent]}%")
and it's showing wrong output like if I have only one job which categories is ruby,rails then this one job is showing two times, one for ruby and one for rails.
Thanks
Your associations are correct, you can retrieve all unique jobs for some categories by following:
Job.joins(:job_categories).joins(:categories).where('lower(categories.parent) LIKE lower(?)', "%#{params[:parent]}%").distinct
This will join the jobs with the intermediate table job_categories and jobs on relevant keys and where clause will then allow you to be selective on what you want to retrieve.
SELECT DISTINCT "jobs" .*
FROM "jobs" INNER
JOIN "job_categories" ON "job_categories" ."job_id" = "jobs" ."id" INNER
JOIN "job_categories" "job_categories_jobs_join" ON "job_categories_jobs_join" ."job_id" = "jobs" ."id" INNER
JOIN "categories" ON "categories" ."id" = "job_categories_jobs_join" ."category_id"
WHERE
(
lower ( categories.parent ) LIKE lower ( "Technology" ) )
Update:
Actually, we don't need to have explicit join to job_categories either, the following should suffice:
Job.joins(:categories).where('lower(categories.parent) LIKE lower(?)', "%#{params[:parent]}%").distinct
SELECT DISTINCT "jobs".* FROM "jobs" INNER JOIN "job_categories" ON "job_categories"."job_id" = "jobs"."id" INNER JOIN "categories" ON "categories"."id" = "job_categories"."category_id" WHERE (lower ( categories.parent ) LIKE lower ( "Technology" ))
Just few other options to fetch and group records with association has_many_through:
# Filtering by query
Job.joins(:categories).select('jobs.id, jobs.name, categories.parent').where('lower(categories.parent) LIKE lower(?)', "Technology").distinct.inspect
# => #<ActiveRecord::Relation [#<Job id: 1, name: "Developer">, #<Job id: 2, name: "Debugger">]>
# Grouping by categories.parent, return a hash
Job.joins(:categories).select('jobs.id, jobs.name, categories.parent').all.distinct.group_by(&:parent)
# => {"Technology"=>[#<Job id: 1, name: "Developer">, #<Job id: 2, name: "Debugger">], "Mechanics"=>[#<Job id: 3, name: "Technic">]}
# Accessing the hash by key
Job.joins(:categories).select('jobs.id, jobs.name, categories.parent').all.distinct.group_by(&:parent)["Technology"]
#=> [#<Job id: 1, name: "Developer">, #<Job id: 2, name: "Debugger">]

Rails string as a foreign key

I have a relation between User and Course (typical enrollment data). A User has_many Course and vice-versa (typical JOIN table scenario).
I am attempting to migrate my previous has_and_belongs_to_many relationship between these two models to a has_many :through relationship. My files currently look like:
class User < ActiveRecord::Base
has_and_belongs_to_many :courses
end
and
class Course < ActiveRecord::Base
has_and_belongs_to_many :users
end
and the table name that joins the two models is courses_users.
I now need to migrate this relationship to the has_many :through association, and also make the column type of user_id a string, as I want to use the g_number (string) attribute of User as the foreign key. Note: I don't care about the performance difference between int and varchar/string.
The short and simple problem is that I need users.g_number to reference enrollments.user_id as a foreign key, and both are strings.
My attempt at a migration and model rework is this:
class User < ActiveRecord::Base
has_many :enrollment
has_many :courses, :through => :enrollment
end
and
class Course < ActiveRecord::Base
has_many :enrollment
has_many :users, :through => :enrollment
end
lastly
class Enrollment < ActiveRecord::Base
belongs_to :course
belongs_to :user
end
then the migration
class ChangeUserIdJoin < ActiveRecord::Migration
def self.up
rename_table :courses_users, :enrollments
end
def self.down
rename_table :enrollments, :courses_users
end
end
Everything works fine here. I can do queries like User.courses and Course.users. But now I want to change the type of the user_id column in the join table to a string so that I can store the g_number (string attribute on User) and join on that instead of the serial id column of User.
When I attempt to change the user_id column type to string in the migration:
class ChangeUserIdJoin < ActiveRecord::Migration
def self.up
change_column :courses_users, :user_id, :string
rename_table :courses_users, :enrollments
end
def self.down
rename_table :enrollments, :courses_users
change_column :courses_users, :user_id, :integer
end
end
the queries Course.users and User.courses start failing (below from Rails console). User.courses returns an empty array (whereas before there are multiple Course objects), and Course.users throws an exception because of mismatched column types (which obviously makes sense):
u = User.take
User Load (0.9ms) SELECT "users".* FROM "users" LIMIT 1
=> #<User id: 1, username: "director", g_number: "g00000000", password_digest: "$2a$10$dvcOd3rHfbcR1Rn/D6VhsOokj4XiIkQbHxXLYjy5s4f...", created_at: "2016-01-06 01:36:00", updated_at: "2016-01-06 01:36:00", first_name: "Director", last_name: "", role: 0, registered: true>
2.1.5 :002 > u.courses
Course Load (0.9ms) SELECT "courses".* FROM "courses" INNER JOIN "enrollments ON "courses"."id" = "enrollments"."course_id" WHERE "enrollments"."user_id" = $1 [["user_id", 1]]
=> #<ActiveRecord::Associations::CollectionProxy []>
2.1.5 :003 > c = Course.take
Course Load (0.7ms) SELECT "courses".* FROM "courses" LIMIT 1
=> #<Course id: 12754, year: 2015, semester: 0, department: 7, course: 101, section: 1, name: "SPA 101 01 - Elementary Spanish I">
2.1.5 :004 > c.users
PG::UndefinedFunction: ERROR: operator does not exist: integer = character varying
LINE 1: ... "users" INNER JOIN "enrollments" ON "users"."id" = "enrollm...
I need to be able to join on enrollments.user_id = users.g_number. What do I need to do in order to change the user_id column to a string type in the Enrollment model/table, and still be able to do Active Record queries like User.courses and Course.users?
Try by specifying the foreign and primary keys in enrollments model, like this
belongs_to :user foreign_key: :user_id, primary_key: :g_number

how to change the sort order for the collection?

please help solve the problem.
tables:
users:
id: integer
name: varchar
posts:
id: integer
title: varchar
user_id: integer
views: integer
models:
User:
class User < ActiveRecord::Base
has_many :posts, dependent: :destroy
end
Posts:
class Post < ActiveRecord::Base
belongs_to :user
end
controller:
def popular_diary
#diaries = User.joins(:posts).group(:user_id).order('SUM(posts.views)')
end
the result #diaries contains a collection users, sorted by the number of views. in ascending order (ASC). but I need to get a collection of users, sorted in descending order (DESC).
I have tried to do so:
#diaries = User.joins(:posts).group(:user_id).order('SUM(posts.views) :DESC')
but I got an error message:
SQLite3::SQLException: near ":DESC": syntax error: SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id" GROUP BY user_id ORDER BY SUM(posts.views) :DESC
#pavan is right. It must be 'DESC' not ':DESC'

ActiveAdmin with two Polymorphic associations not filtering properly

We have a Photo model that is associated with a Tag model. The tag model is a polymorphic table that can be either a Sport or a Place.
I've setup two has many through associations for Sport and Place in the Photo model. In my active admin filter, I have:
filter :sports_id, collection: proc { Sport.order('name').all }, as: :select
filter :places_id, collection: proc { Place.order("name").all }, as: :select
This works fine if I select a sport or a place. But if I select both sport and place, it will always return zero results. It seems that it is based on how it is joining. Here is the SQL ActiveAdmin will generate:
SELECT COUNT(DISTINCT count_column)
FROM (SELECT "photos"."id" AS count_column FROM "photos"
LEFT OUTER JOIN "tags" ON "tags"."photo_id" = "photos"."id"
LEFT OUTER JOIN "sports" ON "sports"."id" = "tags"."taggable_id" AND tags.taggable_type = 'Sport'
LEFT OUTER JOIN "tags" "tags_photos_join" ON "tags_photos_join"."photo_id" = "photos"."id"
LEFT OUTER JOIN "places" ON "places"."id" = "tags"."taggable_id" AND tags.taggable_type = 'Place'
WHERE "photos"."deleted_at" IS NULL AND "sports"."id" = 1 AND "places"."id" = 1 LIMIT 30 OFFSET 0) subquery_for_count
If you have both a sport and a place associated with a photo, this count will return zero no matter what based on how the joins/where statements are setup.
Here is my model setup:
class Photo < ActiveRecord::Base
has_many :tags
has_many :sports, :through => :tags
has_many :places, :through => :tags
end
class Tag < ActiveRecord::Base
belongs_to :photo
belongs_to :taggable, polymorphic: true
belongs_to :sport, foreign_key: 'taggable_id', conditions: "tags.taggable_type = 'Sport'"
belongs_to :place, foreign_key: 'taggable_id', conditions: "tags.taggable_type = 'Place'"
end
class Sport < ActiveRecord::Base
has_many :tags
end
class Place < ActiveRecord::Base
has_many :tags
end
Here is the output from my console:
Started GET "/admin/photos?utf8=%E2%9C%93&q%5Bsports_id_eq%5D=1&q%5Bplaces_id_eq%5D=1796&commit=Filter&order=id_desc" for 192.168.2.72 at 2014-09-03 14:28:55 -0700
Processing by Admin::PhotosController#index as HTML
Parameters: {"utf8"=>"✓", "q"=>{"sports_id_eq"=>"1", "places_id_eq"=>"1796"}, "commit"=>"Filter", "order"=>"id_desc"}
AdminUser Load (0.6ms) SELECT "admin_users".* FROM "admin_users" WHERE "admin_users"."id" = 1 LIMIT 1
(1.7ms) SELECT COUNT(DISTINCT count_column) FROM (SELECT "photos"."id" AS count_column FROM "photos" LEFT OUTER JOIN "tags" ON "tags"."photo_id" = "photos"."id" LEFT OUTER JOIN "sports" ON "sports"."id" = "tags"."taggable_id" AND tags.taggable_type = 'Sport' LEFT OUTER JOIN "tags" "tags_photos_join" ON "tags_photos_join"."photo_id" = "photos"."id" LEFT OUTER JOIN "places" ON "places"."id" = "tags_photos_join"."taggable_id" AND tags.taggable_type = 'Place' WHERE "photos"."deleted_at" IS NULL AND "sports"."id" = 1 AND "places"."id" = 1796 LIMIT 30 OFFSET 0) subquery_for_count
Sport Load (1.1ms) SELECT "sports".* FROM "sports" WHERE "sports"."deleted_at" IS NULL ORDER BY name
Place Load (3.1ms) SELECT "places".* FROM "places" WHERE "places"."deleted_at" IS NULL ORDER BY name
Rendered /Users/admin/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/activeadmin-0.6.3/app/views/active_admin/resource/index.html.arb (453.4ms)
Completed 200 OK in 466ms (Views: 451.4ms | ActiveRecord: 7.1ms)
Help is much appreciated.
Thanks!
UPDATE
I still haven't found a solution to this. Any help is much appreciated.

Rails 3: has_many :uniq issue

Here is a sample model setup (barebone Rails 3.0.5):
class Post < ActiveRecord::Base
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :post
belongs_to :user
end
class User < ActiveRecord::Base
has_many :comments
has_many :commented_posts, through: :comments, source: :post, uniq: true
end
Now the following works correctly:
ruby-1.9.2-p0 > user.commented_posts.count
SQL (0.2ms) SELECT COUNT(DISTINCT "posts".id) FROM "posts" INNER JOIN "comments" ON "posts".id = "comments".post_id WHERE (("comments".user_id = 1))
=> 1
But adding condition makes active record 'forget' about uniq: true bit:
ruby-1.9.2-p0 > user.commented_posts.where("posts.id != 42").count
SQL (0.2ms) SELECT COUNT(*) FROM "posts" INNER JOIN "comments" ON "posts".id = "comments".post_id WHERE (("comments".user_id = 1)) AND (posts.id != 42)
=> 2
Bug? Or what am I missing?
edit:
all works:
ruby-1.9.2-p0 > user.commented_posts.where("posts.id != 42").all
Post Load (0.3ms) SELECT DISTINCT "posts".* FROM "posts" INNER JOIN "comments" ON "posts".id = "comments".post_id WHERE (("comments".user_id = 1)) AND (posts.id != 42)
=> [#<Post id: 1, created_at: "2011-03-07 12:17:30", updated_at: "2011-03-07 12:17:30">]
explicit uniq too:
ruby-1.9.2-p0 > user.commented_posts.where("posts.id != 42").uniq.count
Post Load (0.2ms) SELECT DISTINCT "posts".* FROM "posts" INNER JOIN "comments" ON "posts".id = "comments".post_id WHERE (("comments".user_id = 1)) AND (posts.id != 42)
=> 1
edit 2
Indeed bug in Rails. I submitted a patch. Please upvote it so it gets through sooner. https://github.com/rails/rails/pull/2924#issuecomment-3317185
I've run into this as well on both 3.0.9 and 3.0.10. I consider it an arel bug, although there might be some reason as to why it behaves this way.
I tried overriding count on the join method...
has_many :commented_posts, through: :comments, source: :post, uniq: true do
def count(column_name = nil, opts = {})
super(column_name || 'users.id', opts.reverse_merge(distinct: true))
end
end
But arel ignores the count method if a condition is present. This is why I think it's a bug.
As a hacking solution, I'm using #comments.count('users.id', distinct: true) to force arel to behave in situations where #comments might have a condition attached in the controller.
I think that the explicit uniq is causing the count to occur from the array in memory rather than doing a count in the database. You can tell this is true from the SELECT DISTINCT rather than SELECT COUNT DISTINCT. This will pull all the entries from the DB into rails and then count them.
It is possible to get the DB to do this by passing a couple of parameters to count
collection.count(:id, :distinct => true) will cause the correct SQL code to be generated.

Resources