has_many :through - sql statement using NULL id - ruby-on-rails

We recently upgraded our rails app from version 3.0.3 to 3.1.0. The application runs successfully for the most part as it did before with one major exception. We have a many-to-many relationship between two models, SurveyDatum and SubGroup, joined via a model called SubGroupSurveyDatum. Here is the code for each:
class SurveyDatum < ActiveRecord::Base
has_many :sub_group_survey_data
has_many :sub_groups, :through => :sub_group_survey_data
end
class SubGroup < ActiveRecord::Base
has_many :sub_group_survey_data
has_many :survey_data, :through => :sub_group_survey_data
end
And as you might expect:
class SubGroupSurveyDatum < ActiveRecord::Base
belongs_to :survey_datum
belongs_to :sub_group
end
If I have a SurveyDatum object that I retrieved previously from the database (lets call it 'sd'), and I invoke the sub_groups method (sd.sub_groups), this is the resulting sql query generated by active record:
SELECT `sub_groups`.* FROM `sub_groups` INNER JOIN `sub_group_survey_data` ON `sub_groups`.`id` = `sub_group_survey_data`.`sub_group_id` WHERE `sub_group_survey_data`.`survey_datum_id` IS NULL
The "IS NULL" part is obviously where the id of my survey data object is supposed to go, however active record fails to use it. The object does indeed have an id, since as mentioned it was persisted and retrieved from the database. This problem only cropped up after we moved to rails 3.1, so I assume there's something I've not done properly in accordance with the new version, but I have no idea. Any ideas? Thank you in advance for your help!

Hmm I used rails 3.1.0 and tried to replicate but all was well. The only case was when I manually set id = nil on the record retrieved from the db. Then I got:
SELECT "authors".* FROM "authors" INNER JOIN "relations" ON "authors"."id" = "relations"."author_id" WHERE "relations"."post_id" IS NULL
What database are you using? I was trying this with sqlite3. Also watch out for certain gems especially those that work with ActiveRecord. I had trouble with this in the past.

We discovered the issue. I had forgotten that the survey_data table has a composite primary key. When we upped to version 3.2.3, and added in the SurveyDatum model the following:
set_primary_key :id
The query finally built and executed properly.

Related

How to select from a model all elements that have no relation to another model

Sorry for the vague title.
I have 3 tables: User, Place and PlaceOwner.
I want to write a scope in the "PlaceOwner" model to get all the "Places" that don't have an owner.
class User < ApplicationRecord
has_one :place_owner
end
class PlaceOwner < ApplicationRecord
belongs_to :user
belongs_to :place
#scope :places_without_owner, -> {}
class Place < ApplicationRecord
has_many :place_owners
end
I tried checking for association in the rails console for each element and it worked. But I don't know how to implement this at scope. I've seen people solve similar problems by writing SQL but I don't have the knowledge to write it that way. I understand that I need to check all IDs from Place to see if they are in the PlaceOwner table or not. But I can't implement it.
For example:
There are 3 records in the "Place" table: House13, House14, House15.
There are 2 records in the "PlaceOwner" table: House13 - User1, House 14 - User2
I want to get House15
I hope I explained clearly what I'm trying to do. Please help or at least tell me where to go. Thanks in advance!
I would use the ActiveRecord::QueryMethods::WhereChain#missing method which was introduced in Ruby on Rails 6.1:
Place.where.missing(:place_owners)
Quote from the docs:
missing(*associations)
Returns a new relation with left outer joins and where clause to identify missing relations.
For example, posts that are missing a related author:
Post.where.missing(:author)
# SELECT "posts".* FROM "posts"
# LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
# WHERE "authors"."id" IS NULL
In older versions:
Place.includes(:place_owners).where(place_owners: { id: nil })
Place.left_joins(:place_owners).where(place_owners: { id: nil })
Another interesting option is using EXISTS. Very often such queries have better performance, but unfortunately rails haven't such feature yet (see discussions here and here)
Place.where_not_exists(:place_owners)
Place.where_assoc_not_exists(:place_owners)
To use these methods you need to use where_exists or activerecord_where_assoc gem

Ruby on Rails - ActiveRecord Association Issues : Queries Not Being Made Properly (singular vs. plural?)

First post I've ever felt I had to make here, normally my questions are answered by just reading other posts but this time I think I'm in too deep to find the proper answer, I've been looking for quite a while. I may have multiple issues going on in my code.
I'm a novice with RoR, just started working with it the last couple of months. I'm attempting to run a test query to make sure my associations are working properly, and based on the errors I'm getting, I feel like I've messed up something simple. I'm setting up a couple of tables for a webapp, I'll focus on 2 tables for now because a fix to this can be applied elsewhere afterwards.
Pertinent information:
-I'm using Devise for user authentication
-I have a pto_requests_controller.rb and registrations_controller.rb (registrations is used with devise as a user controller)
-In the database, the table names are 'users' and 'pto_requests'
I have a pto_request model (filename is 'pto_request.rb'):
class PtoRequest < ApplicationRecord
belongs_to :user, optional: true
end
And a user model (filename is 'user.rb'):
class User < ApplicationRecord
has_many :pto_requests
end
To create associations in the database, I used this migration:
class AddUsersToPtorequests < ActiveRecord::Migration[5.1]
def change
add_reference :pto_requests, :users, foreign_key: true
end
end
The foreign key was created in the 'pto_requests' table and called 'users_id'
I can query the tables individually with no issues, but once I try to access data like, for example, user.fname through pto_requests, it errors out. As an example query I've tried:
<%= PtoRequest.joins(:user)
.select('user.fname as user_name').first.user_name%>
I receive the following error:
Mysql2::Error: Unknown column 'user.fname' in 'field list': SELECT user.fname >as user_name FROM pto_requests INNER JOIN users ON users.id = >pto_requests.user_id ORDER BY pto_requests.id ASC LIMIT 1
My current theory is it has something to do with an issue in the end of the generated SQL syntax as it is looking for 'user_id' but this does not exist, only 'users_id' does. Any help is greatly appreciated, and if any more information is required please let me know.
You have missunderstood the associations completely.
belongs_to creates a one-to-one or one-to-many association and places the foreign key column on this models table:
class PtoRequest < ApplicationRecord
belongs_to :user, optional: true
end
This association thus will reference pto_requests.user_id. You have added the foreign key to the wrong table.
To generate the correct migration run:
rails g migration add_user_to_pto_requests user:references
The enitity you are referencing should always be singular.

Data type of attribute on join model in Active Record/Rails 3.2/PostgreSQL database

I have models Client and Sessions and a join model ClientSessionAssignment that associates the two. ClientSessionAssignment has one join column score which is defined as decimal type in schema.rb. I'm using has_many with select to preload score values when querying instances of Client as follows:
class Client < ActiveRecord::Base
has_many :sessions,
:through => :client_session_assignments,
:select => 'sessions.*, client_session_assignments.score'
end
Despite score being defined as decimal in the database, when it is accessed this way, Active Record represents it as a string, i.e. Client.first.sessions.first.score.class yields String when I would expect it to yield BigDecimal. Consequently, I have to cast or convert scores obtained this way to BigDecimal before I can perform any calculations on them.
UPDATES
It turns out that only the postgresql database adapter exhibits this strange behaviour. I have filed https://github.com/rails/rails/issues/13044 to track this in the Rails bug tracker on GitHub: full test case is at https://gist.github.com/rcook/7670071.
Seems like activerecord isn't type casting properly through the association. Try overwriting the accessor method in your join model:
class ClientSessionAssignment < ActiveRecord::Base
def score
ActiveRecord::ConnectionAdapters::Column.value_to_decimal super
end
end
This should return a BigDecimal when you call Client.first.sessions.first.score
This is a bug in Rails 3.2.x:
https://github.com/rails/rails/issues/13044
It's been fixed in Rails 4.0.1.

Rails Association Issue

I don't seem to be understanding this "association" business in rails fully...hoping someone can help.
Model One
class Vendor < ActiveRecord::Base
has_many :products
end
Model Two
class Product < ActiveRecord::Base
belongs_to :vendor
end
When I go to find all "products" that are currently supported and then associate there vendor names...I'm not getting the correct results:
Product.where("is_supported = true").joins(:vendor)
The resulting query is:
SELECT `products`.* FROM `products` INNER JOIN `vendors` ON `vendors`.`id` = `products`.`vendor_id` WHERE (is_supported = true)
The issue is SELECT products.* instead of SELECT *
What am I missing to get the vendor table to join its own fields with the products table?
That is because you are making a JOIN and now you are working with 2 tables at same time. Then , rails makes a specific query.
An initial comment: When using Ruby on Rails (or any other SQL hiding framework) one should not worry (too much) about the underlying SQL queries. They are generated as needed (and might be 'sub-optimal')
To answer your question: 'How can I get the vendor information from a product object' (or that is at least how I read it)
The belongs_to, has_many and other associations add an attribute to the implementing class (in your case the Product and Vendor classes). You can use this attribute to get the information of the associated object.
p = getSomeProduct(...)
v = p.vendor
vendorName = p.vendor.name # or use v.name of course
This code will probably (can't check to be sure sure ATM) generate and execute multiple SQL queries. This is the price to pay to use a framework that hides SQL from the developer.
Please read the guide to Active Record Associations for more information about associations in Ruby on Rails.

Finding nil has_one associations in where query

This may be a simple question, but I seem to be pulling my hair out to find an elegant solution here. I have two ActiveRecord model classes, with a has_one and belongs_to association between them:
class Item < ActiveRecord::Base
has_one :purchase
end
class Purchase < ActiveRecord::Base
belongs_to :item
end
I'm looking for an elegant way to find all Item objects, that have no purchase object associated with them, ideally without resorting to having a boolean is_purchased or similar attribute on the Item.
Right now I have:
purchases = Purchase.all
Item.where('id not in (?)', purchases.map(&:item_id))
Which works, but seems inefficient to me, as it's performing two queries (and purchases could be a massive record set).
Running Rails 3.1.0
It's quite common task, SQL OUTER JOIN usually works fine for it. Take a look here, for example.
In you case try to use something like
not_purchased_items = Item.joins("LEFT OUTER JOIN purchases ON purchases.item_id = items.id").where("purchases.id IS null")
Found two other railsey ways of doing this:
Item.includes(:purchase).references(:purchase).where("purchases.id IS NULL")
Item.includes(:purchase).where(purchases: { id: nil })
Technically the first example works without the 'references' clause but Rails 4 spits deprecation warnings without it.
A more concise version of #dimuch solution is to use the left_outer_joins method introduced in Rails 5:
Item.left_outer_joins(:purchase).where(purchases: {id: nil})
Note that in the left_outer_joins call :purchase is singular (it is the name of the method created by the has_one declaration), and in the where clause :purchases is plural (here it is the name of the table that the id field belongs to.)
Rails 6.1 has added a query method called missing in the ActiveRecord::QueryMethods::WhereChain class.
It returns a new relation with a left outer join and where clause between the parent and child models to identify missing relations.
Example:
Item.where.missing(:purchase)

Resources