Rails Scoping for Existence - ruby-on-rails

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 != ''")

Related

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])}

Rails 3 ActiveRecord#includes bug?

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

Order an association in Active Record to override default sort order defined in association

So I have a class called agency and in that class I have the following:
class Agency
has_many :users, order: 'last_name ASC, first_name ASC'
end
And, when I do the following:
irb(main):004:0> agency.users.order('active desc').pluck(:active)
and that generates the following
SQL (22.0ms) SELECT "users"."active" FROM "users" WHERE "users"."agency_id" = 4040 ORDER BY last_name ASC, first_name ASC, active desc
So, what I want is to override the order in the agency class and not have it sort by last_name or first_name. How can I do that?
Use reorder
agency.users.reorder('active desc').pluck(:active)
The SQL generated would be
SELECT "users"."active" FROM "users" WHERE "users"."agency_id" = 4040 ORDER BY active desc
From the Guides,
The reorder method overrides the default scope order, for example:
class Article < ActiveRecord::Base
has_many :comments, -> { order('posted_at DESC') }
end
Article.find(10).comments.reorder('name')
The SQL that would be executed:
SELECT * FROM articles WHERE id = 10
SELECT * FROM comments WHERE article_id = 10 ORDER BY name
In case the reorder clause is not used, the SQL executed would be:
SELECT * FROM articles WHERE id = 10
SELECT * FROM comments WHERE article_id = 10 ORDER BY posted_at DESC

How to pass a Relation as a subquery in Rails 4?

The example below works in Rails 3 but fails in Rails 4.
The reason is that the Relation returned by accessing the association now uses actual database parameter rather than interpolating the "owning" id.
class Blog < ActiveRecord::Base
has_many :posts, :dependent => :destroy
end
class Post < ActiveRecord::Base
belongs_to :blog
end
b = Blog.first # provided something exists of course
query = Post.where(id: b.posts.where("'complicated query' = ''")); 1 # just to avoid printing in console
puts query.to_sql
# SELECT "posts".* FROM "posts" WHERE "posts"."id" IN (SELECT "posts"."id" FROM "posts" WHERE "posts"."blog_id" = $1 AND ('complicated query' = ''))
query.to_a
# raises the error:
# PG::UndefinedParameter: ERROR: there is no parameter $1
# LINE 1: ...M "posts" WHERE "posts"."blog_id" = $1 AND
# ^
Note the $1 parameter in the subquery which is obviously not provided from the main query (thus the error).
So the question is how can we do the same thing in Rails 4 now (preferably with minimal changes)?
Answering my own question.
This actually isn't a breaking change in Rails 4. It should still works as expected.
It is squeel gem that breaks it https://github.com/ernie/squeel/issues/272

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