Rails 3 ActiveRecord#includes bug? - ruby-on-rails

I have the following models
class Account < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
attr_accessible :email
has_many :accounts
end
As far as i know, in order to select all accounts that for all users with email match a pattern, we must do a join
Account.joins(:user).where("users.email ILIKE ?", '%pattern%') -> work
And here comes the magic, replacing includes with joins, still work like a charm
Account.includes(:user).where("users.email ILIKE ?", '%pattern%') -> work
But
Account.includes(:user).where("users.email ILIKE ?", '%pattern%').count -> error
Any explanation ? isn't includes just for Eager Loading only ?

Because without explicit reference includes loads relation in a separate query. Take a look at rails console:
[11] pry(main)> Account.includes(:user)
AccountsUser Load (4.6ms) SELECT "accounts".* FROM "accounts"
User Load (11.7ms) SELECT "users".* FROM "users" WHERE "users"."id" IN (...
[11] pry(main)> Account.includes(:user).where("users.email ILIKE ?", '%pattern%').to_sql
SELECT "accounts".* FROM "accounts" WHERE (users.email ILIKE '%admin%')
That is why you are getting an error - users table is not referenced in a query. To reference users table use either references or eager_load:
Account.includes(:user).references(:users).where("users.email ILIKE ?", '%pattern%').count
or
Account.eager_load(:user).where("users.email ILIKE ?", '%pattern%').count
Note that includes works with association names while references needs the actual table name

Related

How can I used Rails "gt" or "lt" with where statement?

I have the following relationship in my model.
class Visit < ApplicationRecord
belongs_to :visitor
end
class Visitor < ApplicationRecord
has_many :visits
end
And I'm trying to do
visitors.where(visits.count.gt(value.to_i))
which throws
NoMethodError (undefined method visits for <Visitor::ActiveRecord_Relation:0x00007ff2f0c77578>):
If I understand correctly, you are to get visitors who has more than certain amount of visits. Then you can try the below way -
Visitor.joins(:visits).group('visitors.id').having('count(visitor_id) > ?', value.to_i)
Query wise what you want is:
SELECT "visitors".* FROM "visitors"
INNER JOIN "visits" ON "visits"."visitor_id" = "visitors"."id"
GROUP BY visitors.id
HAVING (count(visits.id) > ?)
LIMIT $1
The reason you want HAVING (count(visits.id) > ?) and not WHERE (count(visits.id) > ?) is that you are setting conditions on an aggregate.
You can create this query either with SQL strings:
Visitor.joins(:visits)
.group('visitors.id') # you can just use :id on PG - other drivers are not as smart
.having('count(visits.id) > ?', 5)
Or with Arel:
Visitor.joins(:visits)
.group(Visitor.arel_table[:id])
.having(Visit.arel_table[:id].count.gt(5))
Using Arel is theoretically more portable as the table names are not hard-coded.

Writing scope to complex multiple associations - Rails 4

I am having challenges writing a scope that displays live_socials created by users belonging to a category_managementgroup called client_group.
Social.rb
belongs_to :user
scope :live_socials, -> {where(['date >= ?', Date.current])}
CategoryManagementgroup.rb
has_many :users
User.rb
has_many :socials
belongs_to :category_managementgroup
the below scope displays all the socials for users in the category_managementgroup called client_group:
users.joins(:socials, :category_managementgroup).client_group.flat_map(&:socials)
i am unsure how to extend the scope to display the live_socials
(socials that have not expired). i tried the below but no success:
users.joins(:socials, :category_managementgroup).client_group.flat_map(&:socials).live_socials
i get the below error:
2.3.0 :264 > ap users.joins(:socials, :category_managementgroup).client_group.flat_map(&:socials).live_socials
User Load (0.5ms) SELECT "users".* FROM "users" INNER JOIN "socials" ON "socials"."user_id" = "users"."id" INNER JOIN "category_managementgroups" ON "category_managementgroups"."id" = "users"."category_managementgroup_id" WHERE "category_managementgroups"."name" = 'Client Group'
Social Load (0.1ms) SELECT "socials".* FROM "socials" WHERE "socials"."user_id" = ? [["user_id", 10]]
NoMethodError: undefined method `live_socials' for #<Array:0x007ff2f9834570>
Try applying live_socials scope before flat_map and add below scope to User model
scope :live_socials, -> { joins(:socials).where(['socials.date >= ?', Date.current])}

Avoiding N+1 in model instance methods by using sort_by instead of order

My app is a CRM for teachers where a Teacher belongs_to an Account that has_many Students who HABTM PhoneNumbers through CallablePhoneNumbers (since, IRL siblings can share one phone number).
(Aside: As a possible complicating factor, PhoneNumbers is Polymorphic. Both Teachers and Students are "Callable"...)
My Issue: I'm trying to avoid N+1 in a students_list view. When viewing a list of 900 students and some metadata, the database hits are pretty terrifying.
app/models/student.rb
class Student < ActiveRecord::Base
...
has_many :phone_numbers, through: :callable_phone_numbers, as: :callable_phone_numbers
...
def last_messaged_at
self.phone_numbers.order(:last_received_message_at).last.try(:last_received_message_at)
# :last_received_message_at is a simple DateTime in the database
end
...
end
When I'm showing a list of students I want to show the last_messaged_at method as a status alongside the student, and I'm attempting to avoid N+1 via .includes()
app/controllers/dashes_controller.rb
class DashesController < ApplicationController
before_action :logged_in_teacher
def show
#teacher = Teacher.includes(account: [{students: [:phone_numbers, :grade_level, :student_groups]}, :grade_levels]).includes(:student_groups).find(#current_teacher.id)
end
end
Yes, there are a lot of other associations in there. I'm focusing this question exclusively on PhoneNumbers, though feedback about my use of .includes() would not be unwelcome, since it does look convoluted.
In the console, I can go...
pry(main)> t = Teacher.includes(account: [{students: [:phone_numbers, :grade_level, :student_groups]}, :grade_levels]).includes(:student_groups).find(3)
Teacher Load (2.3ms) SELECT "teachers".* FROM "teachers" WHERE "teachers"."id" = ? LIMIT 1 [["id", 3]]
Account Load (0.4ms) SELECT "accounts".* FROM "accounts" WHERE "accounts"."id" IN (3)
Student Load (8.2ms) SELECT "students".* FROM "students" WHERE "students"."account_id" IN (3)
CallablePhoneNumber Load (7.3ms) ... ETC
pry(main)> t.account.students.first.phone_numbers
=> [#<PhoneNumber:0x007fddcc59ac98
id: 15,
number: ... ETC
...to get phone_numbers without an additional PhoneNumber Load. However, when I...
pry(main)> t.account.students.first.last_messaged_at
PhoneNumber Load (0.4ms) SELECT "phone_numbers".* FROM "phone_numbers" INNER JOIN "callable_phone_numbers" ON "phone_numbers"."id" = "callable_phone_numbers"."phone_number_id" WHERE "callable_phone_numbers"."callable_id" = ? AND "callable_phone_numbers"."callable_type" = ? ORDER BY "phone_numbers"."last_received_message_at" DESC LIMIT 1 [["callable_id", 3], ["callable_type", "Student"]]
=> Thu, 06 Aug 2015 18:01:12 UTC +00:00
I'm unexpectedly forced to ping the database again, when I would've thought those PhoneNumbers were already in memory.
I felt like an instance method was most appropriate for this, but maybe it should be a helper that I pass the Collection of Phone Numbers to? Even if that's the case, it's still unclear to me why the instance method can't "see" the loaded PhoneNumbers.
Please try sort_by if you have already eager loaded the associations.
self.phone_numbers.sort_by { |pn| pn.last_received_message_at || Time.now - 20.year }.last.try(:last_received_message_at)

Rails Scoping for Existence

I am running Rails 4.0. Ruby 2.0
For signing up, I only ask users to provide me with there email in the new page.
In the update action, I then ask users for their name.
When listing users in the Index action, I only want to show users have updated their name.
I know I need to scope based on if users have updated their name.
User Model
scope :name, where(name: true)
User Controller
def index
#users = User.name
end
Error
undefined method `gsub' for #
I Think the issue is the way, I am calling the scope. I might need to use exists?
Any help is greatly, appreciated. Thank you.
Personally, just to be a little more conventional, I would use
class User < ActiveRecord::Base
scope :has_name, where("users.name != ''")
end
This way, when your model gets joined with another, you won't introduce a column ambiguity in the event multiple tables have a name column
Consider this example
$ rails new app
$ cd app
$ rails g resource User name:string company_id:integer
$ rails g resource Company name:string
$ rake db:migrate
Our models
class User < ActiveRecord::Base
belongs_to :company
scope :has_name, where("name != ''")
end
class Company < ActiveRecord::Base
has_many :users
end
A problem
$ rails c
irb> User.has_name.join(:company)
Oh noes!
User Load (0.4ms) SELECT "users".* FROM "users" INNER JOIN
"companies" ON "companies"."id" = "users"."company_id" WHERE
(name != '')
SQLite3::SQLException: ambiguous column name: name: SELECT
"users".* FROM "users" INNER JOIN "companies" ON
"companies"."id" = "users"."company_id" WHERE (name != '')
ActiveRecord::StatementInvalid: SQLite3::SQLException: ambiguous
column name: name: SELECT "users".* FROM "users" INNER JOIN
"companies" ON "companies"."id" = "users"."company_id" WHERE
(name != '')
Let's fix the scope
class User < ActiveRecord::Base
belongs_to :company
scope :has_name, where("users.name != ''")
end
Re-run our query
irb> reload!
irb> User.has_name.join(:company)
Proper output
User Load (0.1ms) SELECT "users".* FROM "users" INNER JOIN "companies"
ON "companies"."id" = "users"."company_id" WHERE (users.name != '')
=> []
You could use:
scope :with_name, where("name <> ''")
Though the above doesn't tell you if they've actually modified their name, just that it isn't blank. If you wanted to track the name column for changes, you could use something like the PaperTrail gem for this.
Based on additional feedback, I'd recommend:
scope :with_name, where("users.name != ''")

Rails + UUID + Eager loading

I have 2 models in my rails app, one with an UUID primary key :
class User < ActiveRecord::Base
belongs_to :country, :foreign_key => 'country_uuid'
end
class Country < ActiveRecord::Base
set_primary_key :uuid
has_many :users
end
When I try something like that:
<% #user = User.find :first, :include => [:country] %>
<%= #user.country.name %>
I have the good result, but I see 2 requests in the log file. Why eager loading is not working when we change the ID key for UUID key ?
User Load (0.4ms) SELECT `users`.* FROM `users` LIMIT 1
Country Load (0.4ms) SELECT `countries`.* FROM `countries` WHERE (`countries`.`uuid` = '1')
And I would have something like:
User Load (0.4ms) SELECT `users`.* FROM `users` INNER JOIN countries ON countries.uuid = users.country_uuid LIMIT 1
Is there a workaround ?
If I change uuid key for id key, but keep the string format to store an uuid, will it be ok ?
Thanks,
Use joins instead of include to get the inner join
includes always issues a 2nd query but not n+1 queries (lazy)
for the direction you are going in user -> 1 country it is not so important
but if you were going the other direction country -> many users
country = Country.first
# => select countries.* from countries where id = xxxx limit 1;
country.users.each do
# select users.* from users where user_id = xxxx;
# this could be bad because of lazy loading, one query per iteration
end
# vs...
country = Country.first.includes(:users)
# => select countries.* from countries where id = xxxx limit 1;
# => select users.* from users where country_uuid IN (xxxx);
country.users.each do
# users are all in memory
end
see http://guides.rubyonrails.org/active_record_querying.html for more info
I don't think the fact you are using UUID should make any difference

Resources