rails select from association using join - ruby-on-rails

I have three models: company, event, event_space
company has many events
event belongs to event space
now I want to get all events from a company where the event_space has virtual attribute set to true
c = Comapny.first
c.events.joins(:event_space).where("event_space.virtual = true")
I'm doing something wrong because I have
ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: event_space.virtual: SELECT "events".* FROM "events" INNER JOIN "event_spaces" ON "event_spaces"."id" = "events"."event_space_id" WHERE "events"."company_id" = 2 AND (event_space.virtual = true)

You can modify your where clause as follows to get it right:
c.events.joins(:event_space).where(event_spaces: {virtual: true})

Related

ActiveModel::MissingAttributeError: missing attribute: project_id when calling to_json

I wrote a below query class, which is throwing a missing attribute error which I didn't put in the select method anywhere. Any idea from where this error might be coming? Why project_id is being searched in the to_json method.
class WorkitemPriceResearchPresenter
def initialize(company)
#company = company
end
def query
#workitems ||= \
#company
.workitems
.joins(
:workitem_category,
:workitem_status,
{ project: [:shipyard, {vessel: :vessel_category}] },
)
.select <<-EOS
workitems.id as id,
projects.sequential_id as sequential_id,
vessels.name as vessel_name,
vessel_categories.code as vessel_category,
shipyards.name as shipyard,
workitems.item_code as item_code,
workitems.description as description,
workitems.unit as unit,
workitems.price_cents as price_cents,
workitems.currency as currency,
workitems.quantity as quantity,
workitem_statuses.name as status,
workitems.discount as discount,
DATE_PART('year', projects.scheduled_from::date) as scheduled_from_year,
projects.scheduled_from as scheduled_from,
DATE_PART('year', projects.scheduled_to::date) as scheduled_to_year,
projects.scheduled_to as scheduled_to,
workitem_categories.name as Category
EOS
end
def as_json
query.to_json
end
end
Error is:
pry(main)> WorkitemPriceResearchPresenter.new(company).query.as_json
Workitem Load (33.7ms) SELECT workitems.id as id,
projects.sequential_id as sequential_id,
vessels.name as vessel_name,
vessel_categories.code as vessel_category,
shipyards.name as shipyard,
workitems.item_code as item_code,
workitems.description as description,
workitems.unit as unit,
workitems.price_cents as price_cents,
workitems.currency as currency,
workitems.quantity as quantity,
workitem_statuses.name as status,
workitems.discount as discount,
DATE_PART('year', projects.scheduled_from::date) as scheduled_from_year,
projects.scheduled_from as scheduled_from,
DATE_PART('year', projects.scheduled_to::date) as scheduled_to_year,
projects.scheduled_to as scheduled_to,
workitem_categories.name as Category
FROM "workitems" INNER JOIN "workitem_categories" ON "workitem_categories"."id" = "workitems"."workitem_category_id" INNER JOIN "workitem_statuses" ON "workitem_statuses"."id" = "workitems"."workitem_status_id" INNER JOIN "projects" ON "projects"."id" = "workitems"."project_id" AND "projects"."deleted_at" IS NULL INNER JOIN "shipyards" ON "shipyards"."id" = "projects"."shipyard_id" AND "shipyards"."deleted_at" IS NULL INNER JOIN "vessels" ON "vessels"."id" = "projects"."vessel_id" AND "vessels"."deleted_at" IS NULL INNER JOIN "vessel_categories" ON "vessel_categories"."id" = "vessels"."vessel_category_id" AND "vessel_categories"."deleted_at" IS NULL WHERE "workitems"."deleted_at" IS NULL AND "workitems"."company_id" = $1 [["company_id", "c61e4368-1a60-464b-8002-9da31bf301e5"]]
ActiveModel::MissingAttributeError: missing attribute: project_id
from /Users/ar/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/activerecord-4.2.11.3/lib/active_record/attribute_methods/read.rb:93:in `block in _read_attribute'
Let me know if I need to provide any more information.
Since you are joining workitems with project, your workitems is supposed to have a project_id attribute in order to join the tables. But it looks like you do not have such column, so you are getting this error.

Scope Order by Count with Conditions Rails

I have a model Category that has_many Pendencies. I would like to create a scope that order the categories by the amount of Pendencies that has active = true without excluding active = false.
What I have so far is:
scope :order_by_pendencies, -> { left_joins(:pendencies).group(:id).order('COUNT(pendencies.id) DESC')}
This will order it by number of pendencies, but I want to order by pendencies that has active = true.
Another try was:
scope :order_by_pendencies, -> { left_joins(:pendencies).group(:id).where('pendencies.active = ?', true).order('COUNT(pendencies.id) DESC')}
This will order by number of pendencies that has pendencies.active = true, but will exclude the pendencies.active = false.
Thank you for your help.
I guess you want to sort by the amount of active pendencies without ignoring categories that have no active pendencies.
That would be something like:
scope :order_by_pendencies, -> {
active_count_q = Pendency.
group(:category_id).
where(active: true).
select(:category_id, "COUNT(*) AS count")
joins("LEFT JOIN (#{active_count_q.to_sql}) AS ac ON ac.category_id = id").
order("ac.count DESC")
}
The equivalent SQL query:
SELECT *, ac.count
FROM categories
LEFT JOIN (
SELECT category_id, COUNT(*) AS count
FROM pendencies
GROUP BY category_id
WHERE active = true
) AS ac ON ac.category_id = id
ORDER BY ac.count DESC
Note that if there are no active pendencies for a category, the count will be null and will be added to the end of the list.
A similar subquery could be added to sort additionally by the total amount of pendencies...
C# answer as requested:
method() {
....OrderBy((category) => category.Count(pendencies.Where((pendency) => pendency.Active))
}
Or in straight SQL:
SELECT category.id, ..., ActivePendnecies
FROM (SELECT category.id, ..., count(pendency) ActivePendnecies
FROM category
LEFT JOIN pendency ON category.id = pendency.id AND pendnecy.Active = 1
GROUP BY category.id, ...) P
ORDER BY ActivePendnecies;
We have to output ActivePendnecies in SQL even if the code will throw it out because otherwise the optimizer is within its rights to throw out the ORDER BY.
For now I developed the following (it's working, but I believe that it's not the best way):
scope :order_by_pendencies, -> { scoped = Category.left_joins(:pendencies)
.group(:id)
.order('COUNT(pendencies.id) DESC')
.where('pendencies.active = ?', true)
all = Category.all
(scoped + all).uniq}

How to left outer joins with conditions

I have this relation:
class Action < ApplicationRecord
has_many :actions_users
I tried to make a query like:
select *
from actions left outer join actions_users
on actions_users.action_id = actions.id and actions_users.user_id = 1
where actions.user_id = 1
Meanwhile, in my experience, in all of the result that I tried,
select *
from actions left outer join actions_users
on actions_users.action_id = actions.id
where actions.user_id = 1 and actions_users.user_id = 1
the join condition code and general condition are in where function.
How can I work it out?
You can pass a string in join query and use the rails table naming conventions for this.
Action.joins("left outer join action_users on (action_users.id = actions.id and action_users.id = 1")).where('action_users.user_id = ? ', 1)
Because you have a general where condition, you can use includes. This will generate a LEFT OUTER JOIN query:
Action.includes(:actions_users).where(actions_users: { user_id: true })
Or if you are using Rails 5+, Active Record provides a finder method left_outer_joins:
Action.left_outer_joins(:actions_users).where(actions_users: { user_id: true })
Action.left_outer_joins(:actions_users).where(user_id: 1)
select *
from actions left outer join actions_users
on actions_users.action_id = actions.id and actions_users.user_id = 1
where actions.user_id = 1
Although you did not ask for it yet, ...
Action.left_outer_joins(:actions_users).where(actions_users: {status: 'active'})
select *
from actions left outer join actions_users
on actions_users.action_id = actions.id and actions_users.user_id = 1
where actions_users.status = 'active'
Up to Rails 7 there is no way to specify conditions directly on an OUTER JOIN, see the documentation for Specifying Conditions on the Joined Tables. The examples shown are suitable for INNER JOINs (as they use .where), but won't work for OUTER JOINs for the same reason.
You could try to specify the OUTER JOIN manually, but will run into problems passing parameters:
Action.joins("LEFT OUTER JOIN action_users ON (action_users.id = actions.id AND action_users.id = :user_id")
So you will need to do parameter substitution somewhat like this:
outer_join_sanitized = ApplicationRecord.sanitize_sql([
"LEFT OUTER JOIN action_users ON (action_users.id = actions.id AND action_users.id = :user_id)",
{ user_id: 22 }
])
And you could then use Actions.joins(outer_join_sanitized). At this point you might agree that just running with raw SQL from the start is the easier way to go.

Find record which doesn't have any associated records with a specific value

I have a couple of models: User and UserTags
A User has_many UserTags
A UserTag belongs_to User
I am trying to find all the Users which don't have a UserTag with name 'recorded' (so I also want users which don't have any tags at all). Given a users relation, I have this:
users.joins("LEFT OUTER JOIN user_tags ON user_tags.user_id = users.id AND user_tags.name = 'recorded'").
where(user_tags: { id: nil })
Is there any other better or more Railsy way of doing this?
Try this:
users.joins("LEFT OUTER JOIN user_tags ON user_tags.user_id=users.id").where("user_tags.name != ? OR user_tags.id is NULL", 'recorded')
This one should work:
users.joins(:user_tags).where.not(user_tags: { name: 'recorded' })
Joins not eager load nested model you should use "Includes or eage_load"
users.eager_load(:user_tags).where.not(user_tags: { name: 'recorded' })
This will use left outer join and you can update your query in where clause.
Same as
users.includes(:user_tags).where.not(user_tags: { name: 'recorded' })
Try this, It will return the users with 0 user_tags :
users = users.joins(:user_tag).where("users.id IN (?) OR user_tags.name != ?",User.joins(:user_tag).group("users.id").having('count("user_tag.user_id") = 0'), "recorded")
Hey you can simply use includes for outer join as user_tags.id is null returns all your record not having user_tags and user_tags.name != 'recorded' returns record having user_tag name is not recorded
users.includes(:user_tags).where("user_tags.id is null or user_tags.name != ?","recorded")
Or you can also used using not in clause as but it is not optimised way for given query:
users.includes(:user_tags).where("users.id not in (select user_id from user_tags) or user_tags.name != ?","recorded")

rails gem will_paginate on top of custom method not working

In my controller, my filter_with_params() method is causing a syntax error in postgres when I try and stack will_paginate on top of it.
In my controller I call:
#styles = Style.filter_with_params(params).paginate(:page => params[:page], :per_page => 6)
Model method:
class Style < ActiveRecord::Base
def self.filter_with_params(params)
scoped = where("styles.item != ''")
if params[:t]
scoped = scoped.joins(:tags)
scoped = scoped.select("distinct styles.*, count(*) AS count")
scoped = scoped.where(tags: { name: params[:t] })
scoped = scoped.group('styles.id')
scoped = scoped.having("count(*) = #{params[:t].size}")
end
scoped
end
basically my filter method is looking for tags, and then i need to paginate on top of those results. Anyone with similar experience?
I'm using Rails 3 on this app
here is the postgres error
PG::Error: ERROR: syntax error at or near "distinct" LINE 1: SELECT COUNT(*) AS count_all, distinct styles.*, count(*) AS...
^
: SELECT COUNT(*) AS count_all, distinct styles.*, count(*) AS count, styles.id AS styles_id FROM "styles" INNER JOIN "tagizations" ON "tagizations"."style_id" = "styles"."id" INNER JOIN "tags" ON "tags"."id" = "tagizations"."tag_id" WHERE "tags"."name" IN ('engagement') AND (styles.polenza_item != '') GROUP BY styles.id HAVING count(*) = 1
Your SQL has a problem. You need to say distinct clause before the count ('count(*) as count_all'). That is, once you remove the first call to the count function, it should work.
SELECT distinct styles.*, count(*) AS count, styles.id AS styles_id FROM "styles" INNER JOIN "tagizations" ON "tagizations"."style_id" = "styles"."id" ...
You can test your query in your rails console:
>> sql = "SELECT distinct styles.*, count(*) AS count, styles.id AS styles_id..."
>> a = ActiveRecord::Base.connection.execute(sql)
>> a[0]
Hope this helps.

Resources