What does ActiveModel::MissingAttributeError in Rails mean in my case? - ruby-on-rails

I have two Postgres tables that look like above.
amount_availables belongs to facilities as shown below:
class AmountAvailable < ApplicationRecord
belongs_to :sequence_number
belongs_to :facility
validates :facility, :presence => true
When I run a complex query that joins these 2 tables, I get the below error (and this error is not consistent):
ActiveModel::MissingAttributeError (missing attribute: facility_id):
Generated SQL:
This is the SQL that's generated:
SELECT "as_of_date", "entity", "facility", "financial_institution", "amount_availables"."amount_available", "amount_availables"."comments", "amount_availables"."last_updated_on", "amount_availables"."last_updated_by" FROM "amount_availables" INNER JOIN "sequence_numbers" ON "sequence_numbers"."sequence_number" = "amount_availables"."sequence_number_id" INNER JOIN "facilities" ON "facilities"."facility_id" = "amount_availables"."facility_id" WHERE (sequence_numbers.as_of_date >= '10/01/2019' and sequence_numbers.as_of_date <= '12/24/2019' AND facilities.entity in ('3C7','HOLD CO','PCM','PC-M','PFSI','PLS','PMIT','POP','QRS','TAG','TRS')) ORDER BY last_updated_on desc
NOTE:
And until last week, I remember I was getting this error inconsistently (it occurred a LOT of times but not ALL the time! But this week I seem to get it pretty much all the time. And this SQL runs just fine on Postgres client against the same database that my Rails app is using).
Does this error mean there should be a column named facility_id in facilities table?
What should I do on my Rails code to fix this?
I even tried renaming the id column in facilities table to facility_id using the Rails migration code below:
Approach 1 - Rails Migration Fix
class ModifyFacilitiesPkColumnName < ActiveRecord::Migration
def change
rename_column :facilities, :id, :facility_id
end
end
But I am still getting the same error even after the above approach (I DID run rake db:migrate and I can see in my postgres client that the id column in facilites HAS changed to facility_id).
What does this error mean precisely, and how do I fix this?

Your Inner Join seems to be incorrect:
SELECT * FROM "amount_availables"
INNER JOIN "sequence_numbers" ON "sequence_numbers"."sequence_number" = "amount_availables"."sequence_number_id"
INNER JOIN "facilities" ON "facilities"."facility_id" = "amount_availables"."facility_id"
WHERE (sequence_numbers.as_of_date >= '10/01/2019' and sequence_numbers.as_of_date <= '12/24/2019' AND facilities.entity in ('3C7','HOLD CO','PCM','PC-M','PFSI','PLS','PMIT','POP','QRS','TAG','TRS')) ORDER BY last_updated_on desc
Instead it should be:
INNER JOIN "facilities" ON "facilities"."id" = "amount_availables"."facility_id"
It should work.

Related

Rails: weird behavior when getting last record while ordering

I'm using Rails 6 and I've noticed a strange behavior in Active Record when trying to get the latest record from a collection. Here is what I have:
session.rb
class Session < ApplicationRecord
has_many :participations
end
participation.rb
class Participation < ApplicationRecord
belongs_to :session
end
When I'm trying to get the latest participation with:
Participation.order(created_at: :desc).last
The SQL query generated looks like:
SELECT "participations".*
FROM "participations"
ORDER BY "participations"."created_at" ASC
LIMIT $1
Note that I did order(created_at: :desc) but the SQL is using ASC.
However, if I change my code to:
Participation.order(created_at: :asc).last
The SQL query is doing the opposite (a DESC):
SELECT "participations".*
FROM "participations"
ORDER BY "participations"."created_at" DESC
LIMIT $1
Does anyone have an explanation as to why it behave this way ? Is it a Rails bug ?
Seems like using last with order is causing this issue. If I remove last, ActiveRecord is generating the correct SQL (using the correct order)
ActiveRecord is optimizing the SQL statement for you. This
Participation.order(created_at: :desc).last
returns the same result as
Participation.order(created_at: :asc).first
But the latter statement is more efficient because it has to traverse fewer rows, so Rails generates SQL as if you had written it that way.

SQL not working for pg

I'm trying to use SQL to get information from a Postgres database using Rails.
This is what I've tried:
Select starts_at, ends_at, hours, employee.maxname, workorder.wonum from events where starts_at>'2018-03-14'
inner join employees on events.employee_id = employees.id
inner join workorders on events.workorder_id = workorders.id;
I get the following error:
ERROR: syntax error at or near "inner"
LINE 2: inner join employees on events.employee_id = employees.id
Sami's comment is correct, but since this question is tagged with ruby-on-rails you can try to use ActiveRecord's API to do the same:
Make sure that your models relations are defined
class Event < ActiveRecord::Base
belongs_to :employee
belongs_to :workorder
end
And then you can do something like:
Event
.where('starts_at > ?', '2018-03-14')
.joins(:employee, :workorder)
or
Event
.joins(:employee, :workorder)
.where('starts_at > ?', '2018-03-14')
And you don't need to worry which one goes first.
In general, it's suboptimal to create the SQL queries in rails if you don't absolutely need to because they're harder to maintain.
You request should look at this :
select starts_at, ends_at, hours, employee.maxname, workorder.wonum
from events
inner join employees on events.employee_id = employees.id
inner join workorders on events.workorder_id = workorders.id
where starts_at>'2018-03-14';

Using joins to query by attribute on associated recored

I currently have this horribly written query:
membership_ids = User.where(skip_membership_renewal: true).includes(:memberships).map(&:membership_ids).flatten
Memberships.where(id: membership_ids)
I have been trying to use joins so that I can just make one query.
Membership.includes(:user).where("user.skip_membership_renewal", true)
However, this doesn't work since I keep getting the error: ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR.
My relationship is:
User has_many :memberships
Membership belongs_to :user
What am I doing incorrectly?
You just have a pluralization error. In Rails, you define models as singular (User) and the database table is pluralized (users).
Membership.includes(:user).where("users.skip_membership_renewal" => true)
That said, you don't need to resort to using SQL literals for such a simple case. There are a bunch of other ways of assembling this query, like the scope option David Aldridge suggested, or either of these:
non_renewing_users = User.where(skip_membership_renewal: true)
Membership.joins(:user).merge(non_renewing_users)
Membership.where(user: non_renewing_users)
What's more is that these both only execute a single SQL query for most adapters because they use subqueries:
SELECT "memberships".*
FROM "memberships"
WHERE "memberships"."user_id" IN (
SELECT "users"."id" FROM "users"
WHERE "users"."skip_membership_renewal" = true
)
You can probably aim to use:
Membership.where(:user => User.skip_membership_renewal)
Add a scope onto User ...
def self.skip_membership_renewal
where(skip_membership_renewal: true)
end
You should find that it runs as a single query.

Rails 4 / Ruby2 / PG9.3: ActiveRecord confuses tables on SQL subselect

In my rails app projects are assigned to users via assignments. Now I want to retrieve the unassigned projects for a specific user. The SQL query:
SELECT * FROM projects WHERE id NOT IN (SELECT project_id FROM assignments WHERE user_id = 1
lists all the unassigned projects for user 1 when entered in PgAdmin.
Here is my rails code:
class User < ActiveRecord::Base
has_many :assignments, dependent: :destroy
has_many :assigned_projects, through: :assignments, source: :project
has_many :unassigned_projects, :class_name => 'Project', :finder_sql => proc {
["SELECT * FROM projects WHERE id NOT IN (SELECT project_id FROM assignments WHERE user_id = ? )", self.id]
}
This raises:
ActiveRecord::StatementInvalid - PG::Error: ERROR: column projects.user_id does not exist
LINE 1: SELECT 1 AS one FROM "projects" WHERE "projects"."user_id" = $1
which is no surprise, because the correct column would be "assignments.user_id". Apparently rails does not respect the second FROM. What is wrong with my code?
Update:
Inspecting the "unassigned_projects"-CollectionProxy reveals, that it contains exactly what it is supposed to contain. So it seems that it is first assembled correctly. However, afterwards PG raises the error, when calling:
if #unassigned_projects.any?
But not if calling:
if #assigned_projects.any?
["SELECT * FROM project WHERE id NOT IN (SELECT project_id FROM assignements a WHERE a.user_id = ?)", self.id]
Also, shouldn't the table be named projects instead of project? RoR usually uses plural forms for table names.
Finally, the correct spelling is probably assignments, without an extra 'e'.
Ok, I found out that the most efficient way to deal with this is to replace the deprecated has_many-finder_sql-definition with a class method for User:
def unassigned_projects
Project.where.not(id: self.assigned_projects.collect(&:id))
end

Rails 4: Joins in ActiveRecord relation lambdas not include when doing a join

I'm creating a Revision system for a project where a base table contains the current revision for a given id, and a revision table contains the data tagged with a given revision, eg:
foos
- id
- revision
foo_revisions
- foo_id
- revision
{data}
For relations between these I have used the lamda syntax to specify conditions on the relation like this:
class Article
belongs_to :product, ->{ joins(:base).where("products.revision = product_revisions.revision") }, :class_name=> "Product::Revision", :primary_key => :product_id
Where article is not revisioned, but product is (Product::Revision is the model that contains the actual data, and is a ActiveRecord::Base mapping to product_revisions, while Product maps to products).
The :base relation is from Product::Revision to Product
This works fine for the normal things like
a = Article.find(..)
a.product
which products the sql (a.product only)
SELECT `product_revisions`.* FROM `product_revisions`
INNER JOIN `products` ON `products`.`id` = `product_revisions`.`product_id`
WHERE `product_revisions`.`product_id` = 406
AND (products.revision = product_revisions.revision) ORDER BY `product_revisions`.`id` ASC LIMIT 1
But when I do Article.joins(:product) it fails, since it doesn't join in the products table:
SELECT `articles`.* FROM `articles` INNER JOIN `product_revisions`
ON `product_revisions`.`product_id` = `articles`.`product_id`
AND (products.revision = product_revisions.revision)
with the error:
Mysql2::Error: Unknown column 'products.revision' in 'on clause'
To me it seems like ActiveRecord simply ignores the joins in the lamba when it does the joins query, which seems stupid. Is this a bug, or is there a better/correct way to do this?
I've encountered a similar problem. Any joins specified in a lambda for a has_many are silently ignored.
I found this in the Rails issues that solves the problem for me:
https://github.com/rails/rails/pull/11518
The author mentions the problem occurring when there is an order clause but I think this muddies the water - it makes no difference whether there is an order clause or not.
I cannot say whether this is a bug or intended behaviour but I suspect the former.

Resources