Rails 3 ActiveRecord: "OR-ing" together multiple bools - ruby-on-rails

I have an ActiveRecord object RaceCarDriver. Three of the fields are boolean: is_from_texas, is_from_arkansas, and is_from_indiana. In the user search interface, the user could select neither to see all results, select is_from_texas to see only drivers from Texas, or select is_from_texas and is_from_indiana to see all drivers from one of those states.
Now, I know this example is a bit contrived, but I wanted to avoid the complexity of the actual app.
My best attempt is along these lines:
#drivers = RaceCarDriver.select('name').
where(params[:check_texas] == 0 || :is_from_texas => params[:check_texas]).
where(params[:check_arkansas] == 0 || :is_from_arkansas => params[:check_arkansas]).
where(params[:check_indiana] == 0 || :is_from_indiana => params[:check_indiana])
However, this ANDS the chained where clauses together, making it so that if Texas and Indiana were both checked a driver would have to be from both states.
Again, I know this is contrived. Any help is appreciated.

RaceCarDriver.select('name').
where("is_from_texas = ? OR is_from_arkansas = ? OR is_from_indiana = ?",params[:is_from_texas],params[:is_from_arkansas],params[:is_from_indiana])

#drivers = RaceCarDriver.select('name')
#drivers = #drivers.where(:is_from_texas => params[:check_texas]) if params[:check_texas]
#drivers = #drivers.where(:is_from_arkansas => params[:check_arkansas]) if params[:check_arkansas]
#drivers = #drivers.where(:is_from_indiana => params[:check_indiana]) if params[:check_indiana]
EDIT
Solution 1:
where_options = { :is_from_texas => params[:check_texas],
:is_from_arkansas => params[:check_arkansas],
:is_from_indiana => params[:check_indiana] }.select{|k,v| v.present? }
where_conditions = where_options.map{|k,v| "#{k} = #{v}"}.join(" OR ")
#drivers = RaceCarDriver.select('name').where(where_conditions)
Solution 2:
#scoped_drivers = RaceCarDriver.select('name')
#drivers = []
#drivers << #scoped_drivers.where(:is_from_texas => params[:check_texas]) if params[:check_texas]
#drivers << #scoped_drivers.where(:is_from_arkansas => params[:check_arkansas]) if params[:check_arkansas]
#drivers << #scoped_drivers.where(:is_from_indiana => params[:check_indiana]) if params[:check_indiana]
#drivers.flatten!

Related

Create key if it doesn't exist in nested hashes

I've been trying to figure out how to write this ruby code more eloquently. Does someone have a better solution?
a[:new] = {} if a[:new].nil?
a[:new].merge!( { new_key => new_value } )
is there a way to write this in a more elegant way? I come across this a lot when dealing with nested hashes that need to check whether an key exist and if not, create it.
Write it as below taking the help of Hash#to_h and NilClass#to_h
a[:new] = a[:new].to_h.merge( { new_key => new_value } )
Example :
hsh1[:a] # => nil
hsh1[:a] = hsh1[:a].to_h.merge({1=>2})
hsh1[:a] # => {1=>2}
hsh2 = {:a => {'k' => 2}}
hsh2[:a] # => {"k"=>2}
hsh2[:a] = hsh2[:a].to_h.merge({1=>2})
hsh2 # => {:a=>{"k"=>2, 1=>2}}
Do this at the beginning:
a = Hash.new{|h, k| h[k] = {}}
then, without caring whether a has a key :new or not, do
a[:new].merge!(new_key => new_value)
or
a[:new][new_key] = new_value

How to refactor complicated logic in create_unique method?

I would like to simplify this complicated logic for creating unique Track object.
def self.create_unique(p)
f = Track.find :first, :conditions => ['user_id = ? AND target_id = ? AND target_type = ?', p[:user_id], p[:target_id], p[:target_type]]
x = ((p[:target_type] == 'User') and (p[:user_id] == p[:target_id]))
Track.create(p) if (!f and !x)
end
Here's a rewrite of with a few simple extract methods:
def self.create_unique(attributes)
return if exists_for_user_and_target?(attributes)
return if user_is_target?(attributes)
create(attributes)
end
def self.exists_for_user_and_target?(attributes)
exists?(attributes.slice(:user_id, :target_id, :target_type))
end
def self.user_is_target?(attributes)
attributes[:target_type] == 'User' && attributes[:user_id] == attributes[:target_id]
end
This rewrite shows my preference for small, descriptive methods to help explain intent. I also like using guard clauses in cases like create_unique; the happy path is revealed in the last line (create(attributes)), but the guards clearly describe exceptional cases. I believe my use of exists? in exists_for_user_and_target? could be a good replacement for find :first, though it assumes Rails 3.
You could also consider using uniqueness active model validation instead.
##keys = [:user_id, :target_id, :target_type]
def self.create_unique(p)
return if Track.find :first, :conditions => [
##keys.map{|k| "#{k} = ?"}.join(" and "),
*##keys.map{|k| p[k]}
]
return if p[##keys[0]] == p[##keys[1]]
return if p[##keys[2]] == "User"
Track.create(p)
end

Better way to build multiple conditions in Rails 2.3

I'm developing with Rails 2.3.8 and looking for a better way to build find conditions.
On search page, like user search, which user sets search conditions, find conditions are depends on the condition which user have chosen, e.g age, country, zip-code.
I've wrote code below to set multiple find conditions.
# Add condition if params post.
conditions_array = []
conditions_array << ['age > ?', params[:age_over]] if params[:age_over].present?
conditions_array << ['country = ?', params[:country]] if params[:country].present?
conditions_array << ['zip_code = ?', params[:zip_code]] if params[:zip_code].present?
# Build condition
i = 0
conditions = Array.new
columns = ''
conditions_array.each do |key, val|
key = " AND #{key}" if i > 0
columns += key
item_master_conditions[i] = val
i += 1
end
conditions.unshift(columns)
# condiitons => ['age > ? AND country = ? AND zip_code = ?', params[:age], params[country], prams[:zip_code]]
#users = User.find(:all,
:conditions => conditions
)
This code works fine but it is ugly and not smart.
Is there better way to build find conditions?
Named scopes could make it a bit more readable, albeit bulkier, while still preventing SQL injection.
named_scope :age_over, lambda { |age|
if !age.blank?
{ :conditions => ['age > ?', age] }
else
{}
end
}
named_scope :country, lambda { |country|
if !country.blank?
{ :conditions => ['country = ?', age] }
else
{}
end
}
named_scope :zip_code, lambda { |zip_code|
if !zip_code.blank?
{ :conditions => ['zip_code = ?', age] }
else
{}
end
}
And then when you do your search, you can simply chain them together:
#user = User.age_over(params[:age_over]).country(params[:country]).zip_code(params[:zip_code])
I have accidentally run on your questions, and even it is old one, here is the answer:
After defining your conditions, you could use it like this:
# Add condition if params post.
conditions_array = []
conditions_array << ["age > #{params[:age_over]}"] if params[:age_over].present?
conditions_array << ["country = #{params[:country]}"] if params[:country].present?
conditions_array << ["zip_code = #{params[:zip_code]}"] if params[:zip_code].present?
conditions = conditions_array.join(" AND ")
#users = User.find(:all, :conditions => conditions) #Rails 2.3.8
#users = User.where(conditions) #Rails 3+

Rails: How to manipulate find(params[])

I am trying to use Object.find(params[]) to only return objects with :stage_id = integer
Here is my controller code
def show
#lesson = Lesson.find(params[:id])
#stage1 = #lesson.units(params[:stage_id] == 1)
#stage2 = #lesson.units(params[:stage_id] == 2)
Each lesson has many units, each unit has either a stage_id = 1 or stage_id = 2, I want #stage1 to become an array with units that only have a stage_id value of 1. The same goes for stage2.
How can I properly use params to return only units that have the indicated table values?
def show
#lesson = Lesson.find(params[:id])
#stage1 = #lesson.units.first(:conditions => { :stage_id => 1 })
#stage2 = #lesson.units.first(:conditions => { :stage_id => 2 })
end
Ref find
#stage1 = Unit.find(:all, :conditions => ["stage_id=? AND lession_id=?" 1, params[:id]])
#stage2 = Unit.find(:all, :conditions => ["stage_id=? AND lession_id=?" 2, params[:id]])
If Units are "always' going to be structured with Stages, one thing you could do to DRY up your code is to have a Stage model. That allows flexibility to add more stages in the future without breaking code. Assuming that relationship is properly established and data is good, you could do something like the following.
controller code
#lesson = Lesson.find(params[:id])
view code (haml)
- for stage in #lesson.stages
= stage.name
- for unit in stage.units
= unit.name

Is possible to print a line in development logger?

I have a some code like this users_controller.rb, I need to print line no 30 in development.log
line#29 def selectrole
line#30 #userrole = RolesUser.find(:all, :conditions =>["p.user_id = ? and p.status = ? ",session[:user_id], params['status']])
line#31 logger.debug print_line(30)
line#32 end
Can I see 30th line in my development.log like this
#userrole = RoleUser.find(:all, :conditions => ["p.user_id = ? and p.status = ? ", 1234, 'Active'])
What is the approach to write "print_line" function? Here is the my print_line code?
def print_line(file_name, line)
counter = 1
printline = "-------- NO SUCH LINE --------"
File.open(file_name, "r") do |infile|
while (line_text = infile.gets)
if counter == line
printline = "#{counter} :: " + line_text
break
end
counter += 1
end
end
printline
end
from this function I am getting like this
#userrole = RolesUser.find(:all, :conditions =>["p.user_id = ? and p.status = ? ",session[:user_id], params['status']])
Is their any way to find and replace the variables with their respective values?
Assuming that you're primarily interested in knowing the contents of your :conditions, why not just do something like this:
def selectrole
conditions = ["p.user_id = ? and p.status = ? ",session[:user_id], params['status']]
logger.debug(conditions)
#userrole = RolesUser.find(:all, :conditions => conditions)
end

Resources