Let's say that I have several default scoping on table
class User < ActiveRecord::Base
default_scope where(:first_name => 'Allen') # I know it's not realistic
default_scope where(:last_name => 'Kim') # I know it's not realistic
default_scope where(:deleted_at => nil)
end
>> User.all
User Load (0.8ms) SELECT `users`.* FROM `users`
WHERE `users`.`first_name` = 'Allen'
AND `users`.`last_name` = 'Kim' AND (`users`.`deleted_at` IS NUL
L)
and when I want to find users with regardless of first_name , only I can do is unscope it and redefining the default scope again
User.unscoped.where(:deleted_at=>nil).where(:last_name=>"Kim")
Is there a way to unscope certain keys like the following?
User.unscoped(:first_name)
No, unscoped does not accept parameters.
it seems that in your case you would be better off defining normal scopes (was: named_scopes).
you should use default_scopes only if you need the data always (or almost) as defined in the default_scope.
Related
We are calculating statistics for our client. Statistics are calculated for each SpecialtyLevel, and each statistic can have a number of error flags (not to be confused with validation errors). Here are the relationships (all the classes below are nested inside multiple modules, which I have omitted here for simplicity):
class SpecialtyLevel < ActiveRecord::Base
has_many :stats,
:class_name =>"Specialties::Aggregate::Stat",
:foreign_key => "specialty_level_id"
.......
end
class Stat < Surveys::Stat
belongs_to :specialty_level
has_many :stat_flags,
:class_name => "Surveys::PhysicianCompensation::Stats::Specialties::Aggregate::StatFlag",
:foreign_key => "stat_id"
......
end
class StatFlag < Surveys::Stats::StatFlag
belongs_to :stat, :class_name => "Surveys::PhysicianCompensation::Stats::Specialties::Aggregate::Stat"
......
end
In the view, we display one row for each SpecialtyLevel, with one column for each Stat and another column indicating whether or not there are any error flags for that SpecialtyLevel. The client wants to be able to sort the table by the number of error flags. To achieve this, I've created a scope in the SpecialtyLevel class:
scope :with_flag_counts,
select("#{self.columns_with_table_name.join(', ')}, count(stat_flags.id) as stat_flags_count").
joins("INNER JOIN #{Specialties::Aggregate::Stat.table_name} stats on stats.specialty_level_id = #{self.table_name}.id
LEFT OUTER JOIN #{Specialties::Aggregate::StatFlag.table_name} stat_flags on stat_flags.stat_id = stats.id"
).
group(self.columns_with_table_name.join(', '))
Now each row returned from the database will have a stat_flags_count field that I can sort by. This works fine, but I run into a problem when I try to paginate using this code:
def always_show_results_count_will_paginate objects, options = {}
if objects.total_entries <= objects.per_page
content_tag(:div, content_tag(:span, "Showing 0-#{objects.total_entries} of #{objects.total_entries}", :class => 'info-text'))
else
sc_will_paginate objects, options = {}
end
end
For some reason, objects.total_entries returns 1. It seems that something in my scope causes Rails to do some really funky stuff with the result set that it gives me.
The question is, is there another method I can use to return the correct value? Or is there a way that I can adjust my scope to prevent this meddling from occurring?
The group statement makes me suspicious. You may want to fire up a debugger and step through the code and see what's actually getting returned.
Is there a special reason you're using a scope and not just an attribute on the SpecialtyLevel model? Couldn't you just add a def on SpecialtyLevel that would function as a "virtual attribute" that just returns the length of the list of StatFlags?
The answer here is to calculate total_entries separately and pass that into the paginate method, for example:
count = SpecialtyLevel.for_participant(#participant).count
#models = SpecialtyLevel.
with_flag_counts.
for_participant(#participant).
paginate(:per_page => 10, :page => page, :total_entries => count)
I need an activerecord query to Match ALL items in a params array.
Lets say user has_many roles. and each role has a name.
when i pass ['actor', 'producer', 'singer']. I expect the query to return me the users with all the those three roles or more.
But my method implementation below would return users with having atleast one role name matching to those in the array passed
My current method gives results based on finding any of the tags, not "MATCH ALL"
class User < ActiveRecord::Base
has_many :roles
def self.filter_by_roles(roles)
User.joins(:roles).includes(:roles).where(:roles => {:name => roles})
end
end
I don't want to do any array operations after the query checking if the returned result objects contain all the roles or not. This is because I need the Active Record Relation object to be returned from this.
Thanks in advance.
Try this.
User.joins(:roles).includes(:roles).where(:roles => {:name => roles}).group('usermail').having("COUNT(DISTINCt role_id) = 3")
assuming that field usermail is using to identify users.
You could try this:
def self.filter_by_roles(roles)
scope = User.joins(:roles).includes(:roles)
roles.each do |role|
scope = scope.where(roles: {name: role})
end
scope
end
It's untested, so I'm not sure whether it works.
If you pass role_ids array ([1,2,3]) you can do smth like this:
def self.filter_by_roles(role_ids)
User.select{|user| (role_ids - user.role_ids).empty?}
end
But if you pass roles by title (['actor', 'producer', 'singer']) you need smth like this:
def self.filter_by_roles(roles)
role_ids = Role.find_all_by_title(roles).map(&:id)
User.select{|user| (role_ids - user.role_ids).empty?}
end
I have a model for user.rb, in which I define a scope for admins, which is users that have the role of admin through a permissions table.
has_many :permissions
has_many :roles, :through => :permissions
The scope works like this:
scope :admins, joins(:permissions).merge(Permission.admin_permissions)
I'd also like to make a scope called non-admins or something like that, which is all users that do NOT have the admin role.
What's the easiest way to do this?
If you want to have an inverted SQL query, you will have to do it yourself manually. There is no built-in ActiveRecord solution.
scope :admins, joins(:permissions).merge(Permission.where("permissions.admin = true"))
scope :non_admins, joins(:permissions).merge(Permission.where("permissions.admin = false"))
If there are a lot of scopes or they are complex, consider excluding them by id:
User.where("id not in (?)", User.admins.pluck(:id))
# or if you are already using admin records
admins = User.admins
User.where("id not in (?)", admins.map(&:id))
Depending on number of rows and complexity of the original query, this could be slower or faster than the previous way.
An easy way, which would not be performant for a lot of users:
admin_user_ids = User.admins.map(&:id)
non_admin_users = User.all.reject { |u| admin_user_ids.include?(u.id) }
I'm using the permanent_records gem in my rails 3.0.10 app, to prevent hard deletes and it seems rails is ignoring my default scope in checking uniqueness
# user.rb
class User < AR::Base
default_scope where(:deleted_at => nil)
validates_uniqueness_of :email # done by devise
end
in my rails console trying to find a user by email that has been deleted results in null, but when signing up for a new account with a deleted email address results in a validation error on the email field.
This is also the case for another model in my app
# group.rb
class Group < AR::Base
default_scope where(:deleted_at => nil)
validates_uniqueness_of :class_name
end
and that is the same case as before, deleting a group then trying to find it by class name results in nil, however when I try to create a group with a known deleted class name it fails validation.
Does anyone know if I am doing something wrong or should I just write custom validators for this behavior?
Try scoping the uniqueness check with deleted_at
validates_uniqueness_of : email, :scope => :deleted_at
This can allow two records with the same email value as long as deleted_at field is different for both. As long as deleted at is populated with the correct timestamp, which I guess permanent_records gem does, this should work.
In my User model, I want to search for whether a user has multiple accounts (aka 'multis').
Users have sessions; sessions are customized to set
creator_id = ID of first user the session was logged in as, and
updater_id = ID of last user the session was logged in as
(Both columns are indexed.)
If I detect that the two are different when a session is saved, I save the session for posterity (and this query), because it means that the user logged in as one and then logged in as the other - aka they're a multi. (To catch the recursive base case, the creator_id is then reset on the current session.)
Here's the code that does it:
class Session < ActiveRecord::SessionStore::Session
attr_accessor :skip_setters
before_save :set_ip
before_save :set_user
def set_user
return true if self.skip_setters
# First user on this session
self.creator_id ||= self.data[:user_id]
# Last user on this session
self.updater_id = self.data[:user_id] if self.data[:user_id]
if self.creator_id and self.updater_id and
self.creator_id != self.updater_id
logger.error "MULTI LOGIN: User #{self.creator.login} and \
#{self.updater.login} from #{self.ip}"
# Save a copy for later inspection
backup = Session.new {|dup_session|
dup_session.attributes = self.attributes
# overwrite the session_id so we don't conflict with the
# current one & it can't be used to log in
dup_session.session_id = ActiveSupport::SecureRandom.hex(16)
dup_session.skip_setters = true
}
backup.save
# Set this session to be single user again.
# Updater is what the user looks for;
# creator is the one that's there to trigger this.
self.creator_id = self.updater_id
end
end
# etc... e.g. log IP
end
The following query does work.
However, it's ugly, not very efficient, and doesn't play well with Rails' association methods. Keep in mind that sessions is an extremely large and heavily used table, and users almost as much.
I'd like to change this into a has_many association (so that all the associational magic works); possibly :through => a :multi_sessions association. It should capture both directions of multihood, not just one like the current association.
How can this be improved?
class User < ActiveRecord::Base
has_many :sessions, :foreign_key => 'updater_id'
# This association is only unidirectional; it won't catch the reverse case
# (i.e. someone logged in first as this user and then as the other)
has_many :multi_sessions, :foreign_key => 'updater_id',
:conditions => 'sessions.updater_id != sessions.creator_id',
:class_name => 'Session'
has_many :multi_users, :through => :multi_sessions,
:source => 'creator', :class_name => 'User'
...
# This does catch both, but is pretty ugly :(
def multis
# Version 1
User.find_by_sql "SELECT DISTINCT users.* FROM users \
INNER JOIN sessions \
ON (sessions.updater_id = #{self.id} XOR sessions.creator_id = {self.id}) AND \
(sessions.updater_id = users.id XOR sessions.creator_id = users.id) \
WHERE users.id != #{self.id}"
# Version 2
User.find(sessions.find(:all, :conditions => 'creator_id != updater_id',
:select => 'DISTINCT creator_id, updater_id').map{|x|
[x.creator_id, x.updater_id]}.flatten.uniq - [self.id])
end
end
I think the problem you're trying to solve of session re-use is a strange one. You're much better off mapping IPs to unique logins by processing your logs to detect sock-puppets or users with multiple accounts. There are more reliable and easier ways to solve your problem.