I'm writing a zombie survival app, and I'm trying to select all my users marked "alive" where :alive is a boolean.
I was writing a private method in my users controller but can't get the ruby right, does anyone have a pointer?
def get_alive
#holder = (User.map {|user| user})
#user = #holder.each {|i| if i.alive #user << i}
end
thanks
Use a scope to find all alive users.
class User < ActiveRecord::Base
scope :alive, where(:alive => true)
# ... the rest of your model ...
end
Then you can do this:
#alive_users = User.alive
You could just select those users directly if User is active record:
User.where(:alive => true)
Or filter for just those users:
User.all.filter(&:alive)
you need to give a bit more details what "holder" is supposed to be... and why you are comparing against 'i'
otherwise:
User.where(:alive => true)
it's a good idea to wrap this in a scope as in Sean Hill's answer
You can even use this syntax in where query
User.where(alive: true)
Or use select over array of object. But select is slow
User.all.select{ |user| user.alive == true}
Related
Say, I have a method called posted_listings, which is supposed to run an ActiveRecord query and return a collection of User.listings where posted: true, with posted? being a Listing class method. So far I have been doing:
class Bar < ActiveRecord::Base
def posted_listings
posted_listings = []
listings.each { |listing| posted_listings << listing if listing.posted? }
posted_listing
end
end
but each time this query runs I start feeling really bad about my skills (or lack of thereof). What is the most efficient way to return a collection of posted listings?
Edit:
posted? is not an attribute, its a class method:
class Listing < ActiveRecord::Base
def posted?
true if quantity >= 1 && has_sellers?
end
end
def has_sellers?
sellers.count >=1 #association to Seller
end
I would recommend adding a scope to your Listing model like this:
scope :posted, -> { where(posted: true) }
Then you can get all posted listings like this:
#user.listings.posted
You can learn more about scopes here if you are interested.
UPDATE
Try this scope instead:
def self.posted
joins(:sellers)
.where('posted = ? AND quantity > ?', true, 0)
.group('listings.id')
.having('COUNT(sellers.id) > ?', 0)
end
Your question is not so clear for me.
You may try:
User.listings.where(posted: true)
to get all users' posted Listings.
Or, saying #useris an User instance:
#user.listings.where(posted: true)
to get posted Listings from an specific user.
I have churches that use songs.
With a specific song id I am trying to get the most recent usage date and the total of usages limited by whichever church the user belongs to.
#usages = Usage.select("MAX(services.date) as date", :song_name, :song_id, "count(song_id) as count_song_id").joins(:service, :song).where(:services => {church_id: current_user.church_id}).group(:song_name, :song_id).order("count_song_id DESC")
The above code seems to be working but I've now started implementing Pundit authorisation and have run into some difficulties. My scope policy is very simple:
class Scope < Scope
def resolve
if user.admin?
scope.all
else
scope.where church_id: current_user.church_id
end
end
end
The problem is how to actually use it with joins. This doesn't seem right but I'm kind of at a loss:
#usages = policy_scope Usage.select("MAX(services.date) as date", :song_name, :song_id, "count(song_id) as count_song_id").joins(:service, :song).group(:song_name, :song_id).order("count_song_id DESC")
So what has ended up working (I think) is:
#usages = policy_scope(Usage.joins :service)
.select("MAX(services.date) as date", :song_name, :song_id, "count(song_id) as count_song_id")
.joins(:service, :song)
.group(:song_name, :song_id)
.order("count_song_id DESC")
The key being policy_scope(Usage.joins :service)
I have a Rails site that logs simple actions such as when people upvote and downvote information. For every new action, an EventLog is created.
What if the user changes his or her mind? I have an after_create callback that looks for complementary actions and deletes both if it finds a recent pair. For clarity, I mean that if a person upvotes something and soon cancels, both event_logs are deleted. What follows is my callback.
# Find duplicate events by searching nearly all the fields in the EventLog table
#duplicates = EventLog.where("user_id = ? AND event = ? AND project_id = ? AND ..., ).order("created_at DESC")
if #duplicates.size > 1
#duplicates.limit(2).destroy_all
end
The above code doesn't quite work because if any of the fields happen to be nil, the query returns [].
How can I write this code so it can handle null values, and/or is there a better way of doing this altogether?
If I understood this correctly,
some of the fields can be nil, and you want to find activity logs that have same user_id, same project_id or project id can be nil.
So I guess this query should work for you.
ActivityLog.where(user_id: <some_id> AND activity: <complementary_id> AND :project_id.in => [<some_project_id>, nil] ....)
This way you would get the complementary event logs where user_id is same and project id may or may not be present
class ActivityLog
QUERY_HASH = Proc.new{ {user_id: self.user_id,
activity: complementary_id(self.id),
and so on....
} }
How about:
# event_log.rb
def duplicate_attr_map
{
:user_id,
:project_id
}
end
def duplicates
attribs = duplicate_attr_map.reject_if(&:blank?)
query = attribs.map { |attr| "#{attr} = ?" }.join(' AND ')
values = attribs.map { |attr| self.send(attr) }
EventLog.where(query, *values).order("created_at DESC")
end
def delete_duplicates(n)
duplicates.limit(n).delete_all if duplicates.size > 1
end
# usage:
# EventLog.find(1).delete_duplicates(2)
not tested, could be improved
I have the following complex method. I'm trying to find and implement possible improvements. Right now I moved last if statement to Access class.
def add_access(access)
if access.instance_of?(Access)
up = UserAccess.find(:first, :conditions => ['user_id = ? AND access_id = ?', self.id, access.id])
if !up && company
users = company.users.map{|u| u.id unless u.blank?}.compact
num_p = UserAccess.count(:conditions => ['user_id IN (?) AND access_id = ?', users, access.id])
if num_p < access.limit
UserAccess.create(:user => self, :access => access)
else
return "You have exceeded the maximum number of alotted permissions"
end
end
end
end
I would like to add also specs before refactoring. I added first one. How should looks like others?
describe "#add_permission" do
before do
#permission = create(:permission)
#user = create(:user)
end
it "allow create UserPermission" do
expect {
#user.add_permission(#permission)
}.to change {
UserPermission.count
}.by(1)
end
end
Here is how I would do it.
Make the check on the Access more like an initial assertion, and raise an error if that happens.
Make a new method to check for an existing user access - that seems reusable, and more readable.
Then, the company limit is more like a validation to me, move this to the UserAccess class as a custom validation.
class User
has_many :accesses, :class_name=>'UserAccess'
def add_access(access)
raise "Can only add a Access: #{access.inspect}" unless access.instance_of?(Access)
if has_access?(access)
logger.debug("User #{self.inspect} already has the access #{access}")
return false
end
accesses.create(:access => access)
end
def has_access?(access)
accesses.find(:first, :conditions => {:access_id=> access.id})
end
end
class UserAccess
validate :below_company_limit
def below_company_limit
return true unless company
company_user_ids = company.users.map{|u| u.id unless u.blank?}.compact
access_count = UserAccess.count(:conditions => ['user_id IN (?) AND access_id = ?', company_user_ids, access.id])
access_count < access.limit
end
end
Do you have unit and or integration tests for this class?
I would write some first before refactoring.
Assuming you have tests, the first goal might be shortening the length of this method.
Here are some improvements to make:
Move the UserAccess.find call to the UserAccess model and make it a named scope.
Likewise, move the count method as well.
Retest after each change and keep extracting until it's clean. Everyone has a different opinion of clean, but you know it when you see it.
Other thought, not related to moving the code but still cleaner :
users = company.users.map{|u| u.id unless u.blank?}.compact
num_p = UserAccess.count(:conditions => ['user_id IN (?) AND access_id = ?', users, access.id])
Can become :
num_p = UserAccess.where(user_id: company.users, access_id: access.id).count
Trying to do a basic filter in rails 3 using the url params. I'd like to have a white list of params that can be filtered by, and return all the items that match. I've set up some scopes (with many more to come):
# in the model:
scope :budget_min, lambda {|min| where("budget > ?", min)}
scope :budget_max, lambda {|max| where("budget < ?", max)}
...but what's the best way to use some, none, or all of these scopes based on the present params[]? I've gotten this far, but it doesn't extend to multiple options. Looking for a sort of "chain if present" type operation.
#jobs = Job.all
#jobs = Job.budget_min(params[:budget_min]) if params[:budget_min]
I think you are close. Something like this won't extend to multiple options?
query = Job.scoped
query = query.budget_min(params[:budget_min]) if params[:budget_min]
query = query.budget_max(params[:budget_max]) if params[:budget_max]
#jobs = query.all
Generally, I'd prefer hand-made solutions but, for this kind of problem, a code base could become a mess very quickly. So I would go for a gem like meta_search.
One way would be to put your conditionals into the scopes:
scope :budget_max, lambda { |max| where("budget < ?", max) unless max.nil? }
That would still become rather cumbersome since you'd end up with:
Job.budget_min(params[:budget_min]).budget_max(params[:budget_max]) ...
A slightly different approach would be using something like the following inside your model (based on code from here:
class << self
def search(q)
whitelisted_params = {
:budget_max => "budget > ?",
:budget_min => "budget < ?"
}
whitelisted_params.keys.inject(scoped) do |combined_scope, param|
if q[param].nil?
combined_scope
else
combined_scope.where(whitelisted_params[param], q[param])
end
end
end
end
You can then use that method as follows and it should use the whitelisted filters if they're present in params:
MyModel.search(params)