includes with multiple levels of foreign tables - ruby-on-rails

Vacancies have matchings, matchings have rooms, rooms have messages
I need to get the vacancies that follow the specific criteria of a matching attribute and then filter them again based on wether they have messages from an employee.
Vacancy.created_this_week
.includes(:matchings, :rooms, :messages)
.where(matchings: {state: ["applied", "accepted", "denied"]})
.where(messages: {from_employee: false}.count
Although I get following:
Can't join 'Vacancy' to association named 'rooms'; perhaps you misspelled it?
I understand the association is based on a matching but how else would I get this to fit in one query since I need to filter out an amount of vacancies too?
EDIT
Based on an answer below I tried
.includes(matchings: { rooms: :messages })
Which gives me
Can't join 'Matching' to association named 'room'; perhaps you misspelled it?
Sanity check:
>> Matching.first.room.messages
=> #<ActiveRecord::Associations::CollectionProxy []>

Try nesting the includes in Hash format:
.includes(matchings: { rooms: :messages })

Related

Find records with at least one association but exclude records where any associations match condition

In the following setup a customer has many tags through taggings.
class Customer
has_many :taggings
has_many :tags, through: :taggings
end
class Tagging
belongs_to :tag
belongs_to :customer
end
The query I'm trying to perform in Rails with postgres is to Find all customers that have at least one tag but don't have either of the tags A or B.
Performance would need to be taken into consideration as there are tens of thousands of customers.
Please try the following query.
Customer.distinct.joins(:taggings).where.not(id: Customer.joins(:taggings).where(taggings: {tag_id: [tag_id_a,tag_id_b]}).distinct )
Explanation.
Joins will fire inner join query and will make sure you get only those customers which have at least one tag associated with them.
where.not will take care of your additional condition.
Hope this helps.
Let tag_ids is array of A and B ids:
tag_ids = [a.id, b.id]
Then you need to find the Customers, which have either A or B tag:
except_relation = Customer.
joins(:tags).
where(tags: { id: tag_ids }).
distinct
And exclude them from the ones, which have at least one tag:
Customer.
joins(:tags).
where.not(id: except_relation).
distinct
INNER JOIN, produced by .joins, removes Customer without Tag and is a source of dups, so distinct is needed.
UPD: When you need performance, you probably have to change your DB schema to avoid extra joins and indexes.
You can search examples of jsonb tags implementation.
Get ids of tag A and B
ids_of_tag_a_and_b = [Tag.find_by_title('A').id, Tag.find_by_title('B').id]
Find all customers that have at least one tag but don't have either of the tags A or B.
#Customer.joins(:tags).where.not("tags.id in (?)", ids_of_tag_a_and_b)
Customer.joins(:tags).where.not("tags.id = ? OR tags.id = ?", tag_id_1, tag_id_2)

Fetching unique records for through relationship

I have following models
class Tale < ActiveRecord::Base
has_many :tale_moral_joins
has_many :morals, through: :tale_moral_joins
has_many :values, through: :morals
the following code returns duplicate ids. I know why, but I want to know how to change that and get only uniqs
tale = Tale.first
tale.association(:values).ids_reader # => [1,2,2,3,3,4]
even if i do
tale.value_ids
the same happens.
I know that the tale first gets the related morals and for each of them the related values. hence duplicate ids. What can be done by way of configuration rather than use .uniq on the returned array
tale.association(:values).ids_reader.uniq
For example
[1,2,2,3,3,4].uniq
This will return => [1, 2, 3, 4]
You can use distinct on the relation to SELECT unique records (SQL uses SELECT DISTINCT):
tale.values.distinct
You can also use pluck with "DISTINCT id":
tale.values.pluck("DISTINCT id")
This avoids an unnecessary GROUP BY statement, of which there can only be one per query.
That should do the trick:
tale = Tale.first
tale.values.group(:id).pluck(:id)
Records will be filtered by the database engine, not the Ruby interpreter. In most cases that is preferable.

Pluck associated model's attribute in Rails query

In my rails app, collections have many projects, and projects have many steps.
I'd like to grab all the ids of steps in a collection's projects, and I'm wondering if I can do it all in one query.
For example, I know I can do the following
step_ids = []
#collection.projects.each do |project|
project.steps.each do |step|
step_ids << step.id
end
end
But is it possible to do something like the following:
#collection.projects.include(:steps).pluck("step.id") // syntax here is not correct
Try this:
Step.joins(:project).where(projects: { collection_id: #collection.id }).pluck(:'steps.id')
Note the use of project for the joins, and then projects for the where clause. The first corresponds to the belongs_to relationship, and the latter is the name of the db table.
edit: in the case of a many-to-many relationship between projects and collections, and assuming a project belongs_to a project_collection (and then has_many :collections, through :project_collection)
Step.joins(:project => :project_collection)
.where(project_collections: { collection_id: #collection.id })
.pluck(:'steps.id')
Unfortunately, I don't think that we could do it through AR in a single query. You could do a nested query below to retrieve it in two queries to the database:
Step.includes(:projects)
.where(projects: { id: Projects.includes(:collections)
.where(collections: { id: #collections.id }).pluck(:id) } )
.pluck(:id)

How to select from a table that has been joined with same model/class/table?

I'm trying to get a count of how many subcontacts every contact has.
Class Contacts
has_many :subcontacts, class_name: "Contact",foreign_key: "supercontact_id"
belongs_to :supercontact, class_name:"Contact"
And here's the activerecord part i have so far that's roughly what i'm trying to do.
Contact.joins{subcontacts.outer}.select(subcontacts.count as subcontact_count)
I think the problem is that the joins portion is looking for a association name and the select part is looking for a table name. The trouble is that the table name is the same table... What's the best way to do this so that it stays as a relation or using SQL so that we can minimize the number of queries so that it isn't an N+1 problem?
Try using
results = Contact.joins(:subcontacts).select("count(subcontacts.id) as count, contacts.id").group("contacts.id")
and count can be fetched as
results.map do |result|
"Contact ID: #{result.id} - Subcontacts Count: #{result['count']}"
end
Contacts.all.each do |contact|
puts contact.name => contact.subcontacts.count
end
OR
Contacts.all.map{|contact| [contact.name => contact.subcontacts.count]}
The above will provide you the hash like answer{contact_name => subcontacts.count}

Datamapper: Sorting results through association

I'm working on a Rails 3.2 app that uses Datamapper as its ORM. I'm looking for a way to sort a result set by an attribute of the associated model. Specifically I have the following models:
class Vehicle
include DataMapper::Resource
belongs_to :user
end
class User
include DataMapper::Resource
has n, :vehicles
end
Now I want to be able to query the vehicles and sort them by the name of the driver. I tried the following but neither seems to work with Datamapper:
> Vehicle.all( :order => 'users.name' )
ArgumentError: +options[:order]+ entry "users.name" does not map to a property in Vehicle
> Vehicle.all( :order => { :users => 'name' } )
ArgumentError: +options[:order]+ entry [:users, "name"] of an unsupported object Array
Right now I'm using Ruby to sort the result set post-query but obviously that's not helping performance any, also it stops me from further chaining on other scopes.
I spent some more time digging around and finally turned up an old blog which has a solution to this problem. It involves manually building the ordering query in DataMapper.
From: http://rhnh.net/2010/12/01/ordering-by-a-field-in-a-join-model-with-datamapper
def self.ordered_by_vehicle_name direction = :asc
order = DataMapper::Query::Direction.new(vehicle.name, direction)
query = all.query
query.instance_variable_set("#order", [order])
query.instance_variable_set("#links", [relationships['vehicle'].inverse])
all(query)
end
This will let you order by association and still chain on other scopes, e.g.:
User.ordered_by_vehicle_name(:desc).all( :name => 'foo' )
It's a bit hacky but it does what I wanted it to do at least ;)
Note: I'm not familiar with DataMapper and my answer might not be within the standards and recommendations of using DataMapper, but it should hopefully give you the result you're looking for.
I've been looking through various Google searches and the DataMapper documentation and I haven't found a way to "order by assocation attribute". The only solution I have thought of is "raw" SQL.
The query would look like this.
SELECT vehicles.* FROM vehicles
LEFT JOIN users ON vehicles.user_id = users.id
ORDER BY users.name
Unfortunately, from my understanding, when you directly query the database you won't get the Vehicle object, but the data from the database.
From the documentation: http://datamapper.org/docs/find.html. It's near the bottom titled "Talking directly to your data-store"
Note that this will not return Zoo objects, rather the raw data straight from the database
Vehicle.joins(:user).order('users.name').all
or in Rails 2.3,
Vehicle.all(:joins => "inner join users on vehicles.user_id = user.id", :order => 'users.name')

Resources