Rails 4.1 scope with includes failed with unknown column - ruby-on-rails-4

After upgrading rails 4.0 to 4.1, Following code is breaking saying
Mysql2::Error: Unknown column 'user_groups.id' in 'where clause
scope :check_access, lambda {|user| includes(:user_groups).where('restrict_access is false or user_groups.id in (?)', user.get_user_group_ids) if user}
What would be the correct syntax to use above with rails 4.1 ?

includes no longer guarantees a join between your source table and the included relationship. Rails may decide to preload the associated information using either a join or through two separate queries.
In addition to includes, you should add a references(:user_groups) clause, which will give ActiveRecord enough information to know what your intentions are.
scope :check_access, lambda { |user|
includes(:user_groups).references(:user_groups).where('restrict_access is false or user_groups.id in (?)', user.get_user_group_ids) if user
}
As a side note, your construction will return either an ActiveRecord relation if user record exists, or nil if it doesn't. You might be better off using the .none method to return an empty collection instead:
scope :check_access, lambda { |user|
if user
includes(:user_groups)
.references(:user_groups)
.where('restrict_access is false or user_groups.id in (?)', user.get_user_group_ids)
else
none
end
}
That way, check_access will always return an ActiveRecord::Relation collection object, so you can safely chain it with other scopes if need be.

Related

Rails 4 Has_Many With Finder_SQL Deprecated

I am upgrading a Rails app to 4.0. I am receiving the following deprecation warning. I have Google'd on this, but have not found anything that tells how to change this.
DEPRECATION WARNING: The :finder_sql association option is deprecated. Please find an alternative (such as using scopes)...
Here is the scope that is causing the warning:
has_many :elective_instructors,
:class_name => "Instructor",
:finder_sql => proc { "SELECT DISTINCT people.* FROM people
INNER JOIN class_sections ON class_sections.instructor_id = people.id
INNER JOIN courses ON courses.id = class_sections.course_id
INNER JOIN taken_classes ON class_sections.id = taken_classes.class_section_id
WHERE
courses.core = FALSE
AND
taken_classes.student_id = #{id}
AND
people.type = 'Instructor'
AND
people.ignore = FALSE" }
Any ideas would be greatly appreciated. Thanks!
As of 4.1, :finder_sql is not just deprecated - it has been completely REMOVED from Rails.
Here is one way to do something similar through the use of scopes. Let's say we have a User class and Job class (so a user can have many jobs). And let's say that we want to find all distinct jobs that this user holds (contrived example, but it illustrates the point) and let's say we want to use custom SQL for this. We can use find_by_sql as follows
class Job < ActiveRecord::Base
scope :distinct_user_jobs, -> (user_id){ find_by_sql(["SELECT DISTINCT jobs.* FROM jobs WHERE jobs.user_id=?", user_id]) }
end
And then pass in the user_id
user = User.first
Job.distinct_user_jobs(user.id)

Using ActiveRecord to search through an object's associations and return instances where all of the associations do/do not contain a certain value

I'm attempting to query an object's associations, and only return objects where all of their associations are of a certain value. For example, if a User has a memberships association, and memberships has an active(boolean) field, I want to return a collection of all users that only have memberships with active: false.
The query I'm working with right now is:
User.includes(:memberships).where(memberships: {active: false})
However, this gives me all users that have inactive memberships, but also all users that have both active: false and active: true memberships. I tried doing an extra .where.not on this, but of course, that returned the same group.
I've considered mapping over the collection of users and creating a new array, kicking out the users with active memberships, but I need to keep the final value as an AR Collection as I continue to query it further in the controller.
I would prefer to stick to using ActiveRecord for this, but if it's not doable and I need to use SQL instead, I'm also open to that.
Any help would be greatly appreciated. Thank you!
Couple things here - you want a LEFT OUTTER joins, so make sure your SQL query is being constructed properly. Check with
User.includes(:memberships).where(memberships: {active: false}).explain
or
User.includes(:memberships).where(memberships: {active: false}).to_sql
Second, try:
User.joins(:memberships).where("memberships.active = ?", false)
or perhaps give .merge a try, which I like to use:
User.joins(:memberships).merge(Membership.inactive)
assuming you have a scope/class method .inactive on the Membership model
def self.inactive
where(active: false)
end
.includes should really only be used if you need the relationship loaded into memory.
EDIT:
Easiest way to exclude any user that has a single (of many) memberships would be to split this up into 2 queries.
active_user_ids = Membership.active.pluck(:user_id).uniq
User.where("id NOT IN (?)", active_user_ids.join(","))
To exclude Users with both a valid and invalid membership try--
User.includes(:memberships).where(memberships: {active: false}).where.not(memberships: {active: true})
Also--you probably want to use #joins instead of #includes. If you don't need access to the Memberships except for the query, there is no need to load that into memory (which #include does).
This article has a nice explanation--
http://tomdallimore.com/blog/includes-vs-joins-in-rails-when-and-where/

Rails4 Deprecation Warning

Rails4 is getting depreciation warning when I am upgrading from rails 3.2 to rails 4.0. I have this query.
Child.find(:all, :include => :children_users, :conditions => "state = 'active' AND owner_id = #{self.id} AND children_users.user_id = #{other_user.id}")
I am getting deprecation warning as follow :-
DEPRECATION WARNING: It looks like you are eager loading table(s) (one of: splits, accounts) that are referenced in a string SQL snippet. For example:
Post.includes(:comments).where("comments.title = 'foo'")
Currently, Active Record recognizes the table in the string, and knows to JOIN the comments table to the query, rather than loading comments in a separate query. However, doing this without writing a full-blown SQL parser is inherently flawed. Since we don't want to write an SQL parser, we are removing this functionality. From now on, you must explicitly tell Active Record when you are referencing a table from a string:
Post.includes(:comments).where("comments.title = 'foo'").references(:comments)
If you don't rely on implicit join references you can disable the feature entirely by setting `config.active_record.disable_implicit_join_references = true`. (called from splits_under_100_percent at /Users/newimac/RailsApp/bank/app/models/user.rb:274)
To solve this problem, I have try like this
Child.includes(:children_users).where(state: active, owner_id: self.id, children_users.user_id = other_user.id).load
or
Child.where{(state: active, owner_id: self.id, children_users.user_id = other_user.id).includes(:children_users)}
But none of them work.
children_users.user_id = other_user.id wrong.
The correct one is: "children_users.user_id" => other_user.id
Thank You for #Zakwan. Finally, this query works.
Child.includes(:children_users).where(state: 'active', owner_id: self.id, "children_users.user_id" => other_user.id).load

How would I change this to prevent numerous queries against the database to check the user role?

Last Updated: 29 Aug 2013 18:54 EST
I have the following module defined and then included into my model. I am using the rolify gem to give my users roles.
module Permissions::Offer
extend ActiveSupport::Concern
included do
# `user` is a context of security
protect do |user, offer|
# Admins can retrieve anything
if user.has_role? :administrator
scope { all }
# ... and view, create, update, or destroy anything
can :view
can :create
can :update
can :destroy
elsif user.present?
# Allow to read any field
can :view
can :create
# Checks offered_by_id keeping possible nil in mind
# Allow sellers to modify/delete their own offers
if offer.try(:offered_by_id) == user.id
can :update
can :destroy
end
else
# Guests can't read the text
cannot :view
end
end
end
end
What I am experiencing is that when I do the following...
respond_with Offer.restrict!(current_user)
It queries the roles table for every offer that is returned. Is there anyway to have it not make this request repeatedly when requesting a list of offers? I'm sure I could cache the response to avoid the database hit, but I'd rather it not hit the cache either.
If I open a rails console and do the following I get the same result:
current_user = User.first
Offer.restrict!(current_user).to_a
I have installed the bullet gem to see if it considers it an N+1 query, and it doesn't not detect it. I believe because the included gets called every time a new instance of offer gets created it fires off this call to verify permissions. That coupled with the fact that rolify does not cache it's user role checks for any length of time makes this less than ideal. I suppose rolify does this to allow for the changing of roles on the fly without having to deal with clearing the cache. As of now the only way I can see to solve this is to implement caching of my own.
I opened an issue with rolify to see if they are interested in creating a more permanent solution. For anyone else that encounters this, here's what I did int eh meantime.
def has_role?(role)
roles = Rails.cache.fetch(roles_for: { object_id: self.object_id }, expires_in: 10.seconds, race_condition_ttl: 2.seconds) { self.roles.map(&:name) }
roles.include?(role)
end
This doesn't do everything the real method does.. but it suits my purposes.
Here is a link to the source for anyone that wishes to implement something like this on all the methods.
https://github.com/EppO/rolify/blob/master/lib/rolify/role.rb

Rails: Invalid single-table inheritance type error

So, I am working on migrating this php site with an existing database which I cannot change over to Rails. There is a table: Quotes with a column named type. Whenever I try and create a model of this and set the type, it tells me the following error:
ActiveRecord::SubclassNotFound (Invalid single-table inheritance type: HOME is not a subclass of Quotes)
I don't understand why it thinks its inheriting because it's not supposed to. My create method looks like this:
quote = Quotes.create(
agent_id: agent.id,
client_id: client.id,
type: 'HOME',
status: 0,
date_created: DateTime.now
)
If I comment out the type, everything works fine. But with the Type it errors.
I resolved this by setting the models inheritance_column to nil. Active Record Models can inherit from a table through the attribute :type, setting the inheritance_column to nil removes that attribute allowing you to have a database column named type
class Quote < ActiveRecord::Base
self.inheritance_column = nil
end
I hate having potential gotchas deep in the code especially in the intial processes like generating a model. Better to just change the reserved word to something else and free yourself up to take advantage of inheritance column later if the need comes up. A cleaner solution is listed here -> rename a database column name using migration
It reads;
Execute $> rails generate migration ChangeColumnName
where, ChangeColumnName is the name of our migration. This can be any name.
Now, edit the generated migration file at db/migrate/_change_column_name.rb
class ChangeColumnName < ActiveRecord::Migration
def change
rename_column :table_name, :old_column, :new_column
end
end
$> rake db:migrate
You will have to edit controller and view files e.g. if the model name is Product then you will likely edit these files
/app/views/products/_form.html.erb
/app/views/products/show.html.erb
/app/controllers/products_controller.erb
/app/views/products/index.html.erb