Rails active record where chaining losing scope - ruby-on-rails

Model Food has scope expired:
Food.rb
class Food < ApplicationRecord
default_scope { where.not(status: 'DELETED') }
scope :expired, -> { where('exp_date <= ?', DateTime.now) }
belongs_to :user
end
In my controller I'm chaining where conditions to filter foods by user and status:
query_type.rb
def my_listing_connection(filter)
user = context[:current_user]
scope = Food.where(user_id: user.id)
if filter[:status] == 'ARCHIVED'
# Line 149
scope = scope.where(
Food.expired.or(Food.where(status: 'COMPLETED'))
)
else
scope = scope.where(status: filter[:status])
end
scope.order(created_at: :desc, id: :desc)
# LINE 157
scope
end
Here is the rails log:
Food Load (2.7ms) SELECT `foods`.* FROM `foods` WHERE `foods`.`status` !=
'DELETED'
AND ((exp_date <= '2020-07-02 09:58:16.435609') OR `foods`.`status` = 'COMPLETED')
↳ app/graphql/types/query_type.rb:149
Food Load (1.6ms) SELECT `foods`.* FROM `foods` WHERE `foods`.`status` != 'DELETED'
AND `foods`.`user_id` = 1 ORDER BY `foods`.`created_at` DESC, `foods`.`id` DESC
↳ app/graphql/types/query_type.rb:157
Why does active records query loses expired scope (and a condition) in line 157?

It is ignored because where doesn't expect scopes like that. But you can use merge instead. Replace
scope = scope.where(
Food.expired.or(Food.where(status: 'COMPLETED'))
)
with
scope = scope.merge(Food.expired)
.or(Food.where(status: 'COMPLETED'))
or
scope = scope.where(status: 'COMPLETED').or(Food.expired)

Related

active record returning all relations of an object that are archived: false

In ruby on rails, I have a users model, this user can have a potential_match as either a mentor or a mentee. These potential_matches can either be archived: true, or archived false and I cannot get this query to work for the life of me.
scope :potential_matches,
-> {
joins('LEFT JOIN potential_matches ON potential_matches.mentor_id = users.id OR potential_matches.mentee_id = users.id')
.where('potential_matches.archived = false')
.where('users.active = true')
}
and the test that always fails is below:
describe '.potential_matches' do
it 'returns only active potential matches' do
user1 = FactoryBot.create(:user)
user2 = FactoryBot.create(:user)
user3 = FactoryBot.create(:user)
user4 = FactoryBot.create(:user)
pot_match1 = FactoryBot.create(:potential_match, {mentor_id: user1.id, mentee_id: user2.id})
pot_match2 = FactoryBot.create(:potential_match, {mentor_id: user2.id, mentee_id: user1.id})
pot_match3 = FactoryBot.create(:potential_match, {mentor_id: user3.id, mentee_id: user4.id, archived: true})
pot_matches = User.potential_matches
expect(pot_matches).to include(pot_match1, pot_match2)
expect(pot_matches).not_to include(pot_match3)
end
end
what am i missing here? its driving me insane.
If what you're trying to do is get users without any archived matches the easiest way is by creating a subquery:
class User < ApplicationRecord
def self.potential_matches
pm = PotentialMatch.arel_table
id = arel_table[:id]
where(
PotentialMatch.where(
pm[:mentor_id].eq(id).or(pm[:mentee_id].eq(id))
).where(archived: true).arel.exists.not
).where(active: true)
end
end
This generates the following SQL:
SELECT "users".*
FROM "users"
WHERE NOT ( EXISTS (SELECT "potential_matches".*
FROM "potential_matches"
WHERE ( "potential_matches"."mentor_id" = "users"."id"
OR "potential_matches"."mentor_id" =
"users"."id" )
AND "potential_matches"."archived" = ?) )
AND "users"."active" = ? /* loading for inspect */
LIMIT ?

Rails query where at least 1 of the associated records contains an attribute

I have a GroupMeetings table and a GroupMeetingsUser table which is a join table between User and GroupMeetings. I want to find all the GroupMeetings where at least 1 of the GroupMeetingsUser has an attribute.
Right now, this works:
#group_meetings = GroupMeeting.where('lang_one_id IN (?) AND lang_two_id IN (?) AND meeting_time >= ?', #user.languages.pluck(:id), #user.languages.pluck(:id), Date.today)
#new_group_meetings_id = []
#group_meetings.each do |meeting|
meeting.group_meetings_user.each do |user|
if(user.user.location === #user.location)
#new_group_meetings_id.push(meeting.id)
end
end
end
#group_meetings = GroupMeeting.where('id IN (?)', #new_group_meetings_id)
But how can I include the .each loop in original GroupMeetings query instead? Like using .joins(:group_meetings_user) to find all the records where at least 1 of the users has an attribute?
class GroupMeeting < ApplicationRecord
has_many :group_meetings_users
has_many :users, through: :group_meetings_users
end
class GroupMeetingsUser < ApplicationRecord
belongs_to :user
belongs_to :group_meeting
validates_presence_of :user_id, :group_meeting_id
validates :user_id, :uniqueness => {:scope => :group_meeting_id, :message => 'can only join each group once.'}
end
UPDATE 1:
GroupMeeting.joins(:users).where(group_meeting: { lang_one_id: #user.languages.pluck(:id), lang_two_id: #user.languages.pluck(:id), meeting_time: DateTime.now..DateTime::Infinity.new}, user: { location: #user.location })
gives the error:
no such column: group_meeting.lang_one_id: SELECT "group_meetings".* FROM "group_meetings" INNER JOIN "group_meetings_users" ON "group_meetings_users"."group_meeting_id" = "group_meetings"."id" INNER JOIN "users" ON "users"."id" = "group_meetings_users"."user_id" WHERE "group_meeting"."lang_one_id" IN (29, 30, 31, 22) AND "group_meeting"."lang_two_id" IN (29, 30, 31, 22) AND ("group_meeting"."meeting_time" >= ?) AND "user"."location" = ?
I suggest the following:
GroupMeeting
.joins('INNER JOIN languages l1 ON languages l1.id = group_meetings.lang_one_id')
.joins('INNER JOIN languages l2 ON languages l2.id = group_meetings.lang_two_id')
.joins(:group_meeting_users => :users)
.where('meeting_time >= ?', Date.today)
.select('group_meetings.*')
.group('group_meetings.id')
.having('users.location = ?', #user.location)
It should be faster than doing sub-selects (using languages.id IN (?) and pluck)
Let me know if that works and/or if you have questions.

Display Similar Items With Having Distinct Count Rails 5.1

I'm trying to display a list of gins that have a similar minimum number of botanicals on my show page. I feel I'm close, but the current output is not right. It's actually just printing the name of the gin a number of times.
Gin Load (1.6ms) SELECT "gins".* FROM "gins" INNER JOIN
"gins_botanicals" ON "gins_botanicals"."gin_id" = "gins"."id" INNER
JOIN "botanicals" ON "botanicals"."id" =
"gins_botanicals"."botanical_id" WHERE "botanicals"."id" IN (4, 10, 3)
AND ("gins"."id" != $1) GROUP BY gins.id HAVING (COUNT(distinct
botanicals.id) >= 3) [["id", 2]]
I have three models; two resources with a joins table:
gin.rb
class Gin < ApplicationRecord
belongs_to :distillery, inverse_of: :gins
accepts_nested_attributes_for :distillery, reject_if: lambda {|attributes| attributes['name'].blank?}
acts_as_punchable
has_many :gins_botanical
has_many :botanicals, through: :gins_botanical
botanical.rb
class Botanical < ApplicationRecord
has_many :gins_botanical
has_many :gins, through: :gins_botanical
gins_botanical.rb
class GinsBotanical < ApplicationRecord
belongs_to :gin
belongs_to :botanical
gins_controller
def show
#gin = Gin.friendly.find(params[:id])
#gin.punch(request)
#meta_title = meta_title #gin.name
#similiar_gins = Gin.joins(:botanicals).where("botanicals.id" => #gin.botanical_ids).where.not('gins.id' => #gin.id).having("COUNT(distinct botanicals.id) >= 3").group("gins.id")
end
so in #similar_gins i am trying to count how many matching botanicals does the current #gin have compared to all the other #gins and if >= 3 return the values.
And in my view:
show.html.erb
<% #similiar_gins.each do |gin| %>
<%= #gin.name %>
<% end %>
I'm suspecting my where is not correct...
Yes, I have the similar feature but I have implemented like below
#gin = Gin.find(params[:id])
if #gin.botanicals.count > 1
#botanicals = #gin.botanical_ids
#gin_ids = Botanical.select('distinct gin_id').where('gin_id IN (?)', #botanicals).limit(10)
#ids = #gin_ids.map(&:gin_id)
#similiar_gins = Gin.where('id IN (?)', #ids).where.not(id: #gin) #=> similar all without current gin
end
This code is converted from my code which is relation is category and jobs, if you need to see my code for showing the similar jobs then it is
def show
#job = Job.find(params[:id])
if #job.categories.count > 1
#category = #job.category_ids
#jobs = JobCategory.select('distinct job_id').where('category_id IN (?)', #category).limit(10)
ids = #jobs.map(&:job_id)
#releted_jobs = Job.where('id IN (?)', ids).where.not(id: #job)
end
end
Hope it helps

Merge ActiveRecord_Relation

In model Banner
belongs_to :segment
belongs_to :basic_component
has_many :state_banners, dependent: :destroy
has_many :states, through: :state_banners
scope :banner_have_zero_cities, lambda { includes(state_banners: :state_banner_cities).where(state_banner_cities: {state_banner_id: nil}) }
scope :banner_by_state, lambda { |state_id| where("state_banners.state_id = ?", state_id) }
scope :banner_by_city, lambda { |city_id| joins(state_banners: :state_banner_cities).where("state_banner_cities.city_id = ?", city_id) }
In controller
def scoped_collection
#banners_cities = Banner.banner_by_city(city_id)
#banners_states =Banner.banner_by_state(city.state_id).banner_have_zero_cities
#banners = #banners_cities.concat(#banners_states)
return #banners.joins(:basic_component)
end
#banners_states.size
=> 1
#banners_cities.size
=> 2
#banners_states.merge(#banners_cities)
SQL (0.2ms) SELECT DISTINCT banners.id FROM banners INNER JOIN state_banners ON state_banners.banner_id = banners.id INNER JOIN state_banner_cities ON state_banner_cities.state_banner_id = state_banners.id WHERE (state_banners.state_id = 3) AND state_banner_cities.state_banner_id IS NULL AND (state_banner_cities.city_id = '260') LIMIT 25 OFFSET 0
=> []
I need 3
i try concat
#banners = #banners_cities.concat(#banners_states)
#banners.size => 3
but
#banners.joins(:basic_component).order("basic_component.order asc").size => 2
CACHE (0.0ms) SELECT COUNT(count_column) FROM (SELECT 1 AS count_column FROM banners INNER JOIN state_banners ON state_banners.banner_id = banners.id INNER JOIN state_banner_cities ON state_banner_cities.state_banner_id = state_banners.id INNER JOIN basic_components ON basic_components.id = banners.basic_component_id WHERE (state_banner_cities.city_id = '260') LIMIT 25 OFFSET 0) subquery_for_count
:(, help
Your post is kind of hard to follow, but try .limit(3) at the end of the query?

Error when combining search scopes

I have a web-service that allows clients to search for articles with query parameters.It works fine if only one parameter is included but fails if I combine search_query and category. This is based on Comfortable_Mexican_Sofa where for_category is found. Even if I remove the order statement i get this error.
error
PG::InvalidColumnReference: ERROR: for SELECT DISTINCT, ORDER BY
expressions must appear in select list LINE 1:
...ms_categories"."label" = 'Company News' ORDER BY pg_search_...
^ : SELECT DISTINCT "comfy_cms_pages".* FROM "comfy_cms_pages" INNER JOIN
"comfy_cms_categorizations" ON
"comfy_cms_categorizations"."categorized_id" = "comfy_cms_pages"."id"
AND "comfy_cms_categorizations"."categorized_type" = $1 INNER JOIN
"comfy_cms_categories" ON "comfy_cms_categories"."id" =
"comfy_cms_categorizations"."category_id" INNER JOIN (SELECT
"comfy_cms_pages"."id" AS pg_search_id,
(ts_rank((to_tsvector('simple',
coalesce("comfy_cms_pages"."content_cache"::text, '')) ||
to_tsvector('simple', coalesce("comfy_cms_pages"."label"::text, ''))),
(to_tsquery('simple', ''' ' || 'austin' || ' ''' || ':')), 0)) AS
rank FROM "comfy_cms_pages" WHERE (((to_tsvector('simple',
coalesce("comfy_cms_pages"."content_cache"::text, '')) ||
to_tsvector('simple', coalesce("comfy_cms_pages"."label"::text, '')))
## (to_tsquery('simple', ''' ' || 'austin' || ' ''' || ':')))))
pg_search_comfy_cms_pages ON "comfy_cms_pages"."id" =
pg_search_comfy_cms_pages.pg_search_id WHERE (layout_id = '1' AND
is_published = 't') AND "comfy_cms_categories"."label" = 'Company
News' ORDER BY pg_search_comfy_cms_pages.rank DESC,
"comfy_cms_pages"."id" ASC, "comfy_cms_pages"."created_at" DESC
app/models/article.rb
class Article < Comfy::Cms::Page
cms_is_categorized
include PgSearch
pg_search_scope :search_by_keywords, against: [:content_cache, :label], using: { tsearch: { any_word: true, prefix: true } }
app/commands/search_articles_command.rb
class SearchArticlesCommand
def initialize(params = {})
#since = params[:since_date]
#keys = params[:search_query]
#category = params[:category]
end
def execute
Article.unscoped do
query = if #since.present?
Article.article.since_date(#since)
else
Article.published_article
end
query = query.for_category(#category) if #category.present?
query = query.search_by_keywords(#keys) if #keys.present?
query.where('').order(created_at: :desc)
end
end
end
comfortable-mexican-sofa/lib/comfortable_mexican_sofa/extensions/is_categorized.rb
module ComfortableMexicanSofa::IsCategorized
def self.included(base)
base.send :extend, ClassMethods
end
module ClassMethods
def cms_is_categorized
include ComfortableMexicanSofa::IsCategorized::InstanceMethods
has_many :categorizations,
:as => :categorized,
:class_name => 'Comfy::Cms::Categorization',
:dependent => :destroy
has_many :categories,
:through => :categorizations,
:class_name => 'Comfy::Cms::Category'
attr_accessor :category_ids
after_save :sync_categories
scope :for_category, lambda { |*categories|
if (categories = [categories].flatten.compact).present?
self.distinct.
joins(:categorizations => :category).
where('comfy_cms_categories.label' => categories)
end
}
end
end
module InstanceMethods
def sync_categories
(self.category_ids || {}).each do |category_id, flag|
case flag.to_i
when 1
if category = Comfy::Cms::Category.find_by_id(category_id)
category.categorizations.create(:categorized => self)
end
when 0
self.categorizations.where(:category_id => category_id).destroy_all
end
end
end
end
end
ActiveRecord::Base.send :include, ComfortableMexicanSofa::IsCategorized
Updated Error
PG::SyntaxError: ERROR: syntax error at or near "."
LINE 4: ...e = 'Class' AND categorized_id = 'comfy_cms_pages'.'id' AND ...
^
: SELECT "comfy_cms_pages".* FROM "comfy_cms_pages" INNER JOIN (SELECT "comfy_cms_pages"."id" AS pg_search_id, (ts_rank((to_tsvector('simple', coalesce("comfy_cms_pages"."content_cache"::text, '')) || to_tsvector('simple', coalesce("comfy_cms_pages"."label"::text, ''))), (to_tsquery('simple', ''' ' || 'austin' || ' ''' || ':*')), 0)) AS rank FROM "comfy_cms_pages" WHERE (((to_tsvector('simple', coalesce("comfy_cms_pages"."content_cache"::text, '')) || to_tsvector('simple', coalesce("comfy_cms_pages"."label"::text, ''))) ## (to_tsquery('simple', ''' ' || 'austin' || ' ''' || ':*'))))) pg_search_comfy_cms_pages ON "comfy_cms_pages"."id" = pg_search_comfy_cms_pages.pg_search_id WHERE "comfy_cms_pages"."layout_id" = $1 AND "comfy_cms_pages"."is_published" = $2 AND (
EXISTS (
SELECT 1 FROM categorizations
WHERE categorized_type = 'Class' AND categorized_id = 'comfy_cms_pages'.'id' AND category_id IN (2)
)) ORDER BY pg_search_comfy_cms_pages.rank DESC, "comfy_cms_pages"."id" ASC
working solution but not a scope and have to be careful of order its being called
def self.for_category(_category)
Comfy::Cms::Categorization.includes(:category).references(:category).select(:categorized).pluck(:categorized_id)
find(ids)
end
I think it's best to override for_category built-in filter of your CMS. Too many joins in that query.
Override for_category like this:
scope :for_category, lambda { |*categories|
if (categories = [categories].flatten.compact).present?
self_ids = "{connection.quote_table_name(self.table_name)}.#{connection.quote_column_name(self.primary_key)}"
self.where(
"EXISTS (" +
Comfy::Cms::Categorization.select('1').
where(categorized_type: self.name).
where('categorized_id' => self_ids).
where(category_id: Comfy::Cms::Category.where(label: categories).pluck(:id)).to_sql +
")"
)
end
}
More on SQL EXISTS usage in Rails you can read in my Rails: SQL EXISTS brief how-to.
More on why you bump into that error you can read in question and answer here.
Specifically, pg_search wants order your results by rank. And for_category wants to select distinct fields of Article only, and doesn't care about search rank. Changing its code to use simple EXISTS instead of complex JOIN query will fix that.
I was able to solve this problem by applying reorder to the result of the pg_search result.
search_command.rb
class SearchArticlesCommand
def initialize(params = {})
#since = params['since_date']
#keys = params['search_query']
#category = params['category']
end
def execute
Article.unscoped do
query = Article.article
query = if #since.present?
query.since_date(#since)
else
query.published
end
query = query.for_category(#category) if #category.present?
query = query.search_by_keywords(#keys).reorder('updated_at DESC') if #keys.present?
query
end
end
end
I also overrode for_category (not required)
article.rb
scope :for_category, (lambda do |category|
published
.joins(:categories)
.group(:id)
.where('comfy_cms_categories.label' => category)
.select('comfy_cms_pages.*')
end)

Resources