How do I handle the where conditions dynamically in squeel? - ruby-on-rails

From this input: {'hearing' => 1} I need to generate this query
Score.joins(:target_disability).where{ (target_disabilities.name == 'hearing') & (round(total_score) >= 1) }
From this input is {'hearing' => 1, 'mobility' => 2}, I need to generate this:
Score.joins(:target_disability).where{ (target_disabilities.name == 'hearing') & (round(total_score) >= 1) | (target_disabilities.name == 'mobility') & (round(total_score) >= 2) }
And so on...
How can this be generalized? Because my input sometimes has 3 or 4 keys... sometime 1...

Assuming your hash is in my_params:
#scores = Score.joins(:target_disability).where do
my_params.map{|k,v| (target_disabilities.name==k) & (round(total_score)>=v) }.inject(:|)
end

Related

Search by array of values in .select query method

I'm having this type of search:
values = ModelName.find(:all, :conditions => ['attr_id IN (SELECT attr_id FROM srv_type_attr WHERE id IN (?))', serv_objt_attr.collect(&:stya_id)])
Witch returns me an array of needed values:
[33458, 33438]
Next i need to check if record exists with select:
serv_objt_attr.select {|array| array.stya_id == values.collect(&:attr_id).uniq}
This is an example what i'm thinking off.
So how to do it with select, so he would walk through all values witch i'm getting from values.
I know that i could to something like
values.collect(&:attr_id).uniq do |val|
serv_objt_attr.select {|array| array.stya_id == val}
end
But i do not thing that this is a good option.
Ruby 1.8.7
Rails 2.3.4
This is a good case for the set intersection operator:
values = ModelName.find(:all, :conditions => ['attr_id IN (SELECT attr_id FROM srv_type_attr WHERE id IN (?))', serv_objt_attr.collect(&:stya_id)])
values & Set.new(serv_objt_attr.map(&:stya_id)
Here's what the & does:
>> values = [1,2,3]
=> [1, 2, 3]
>> other_array = [1,5,9,3]
=> [1, 5, 9, 3]
>> values & other_array
=> [1, 3]

Grouping hash by value and get count of the values under one group in Rails

I have a code that gets the list of checkins given a span of time. See code below.
from = Time.zone.now.beginning_of_month
to = Time.zone.now.end_of_month
customer_checkins = CustomerCheckin.where(account_id: seld.id, created_at: from..to)
The code would then give me all checkin objects that satisfies the given condition. The next thing that I need to do is to group the list of checkins per customer. So I have this code to do that.
group_customer_id = customer_checkins.group(:customer_id).count
Grouping it by customer id will then result to a hash. See example below.
{174621=>9,180262=>1,180263=>1,180272=>1,180273=>3,180274=>3,180275=>4,180276=>3,180277=>2,180278=>4,180279=>4,180280=>3,180281=>5,180282=>8}
I would like now to get the count of customers with the same checkin count - how many customers have 9 checkins, 5 checkins, etc. So given the hash above. I am expecting an output like this:
{9 => 1, 8=> 1, 5 => 1, 4=> 3, 3 => 4, 2=> 1, 1 => 3}
h.each_with_object({}) {|(k,v), h| h[v] = h[v].to_i + 1}
# => {9=>1, 1=>3, 3=>4, 4=>3, 2=>1, 5=>1, 8=>1}
Get the values from hash like:
customer_array = {174621=>9,180262=>1,180263=>1,180272=>1,180273=>3,180274=>3,180275=>4,180276=>3,180277=>2,180278=>4,180279=>4,180280=>3,180281=>5,180282=>8}.values
customer_count = Hash.new(0)
customer_array.each do |v|
customer_count[v] += 1
end
puts customer_count
a = {174621=>9,180262=>1,180263=>1,180272=>1,180273=>3,180274=>3,180275=>4,180276=>3,180277=>2,180278=>4,180279=>4,180280=>3,180281=>5,180282=>8}
result = a.map{|k,v| v}.each_with_object(Hash.new(0)) { |word,counts| counts[word] += 1 }
# => {9=>1, 1=>3, 3=>4, 4=>3, 2=>1, 5=>1, 8=>1}

Transformation of query to active record

To perform a range query we follow something akin to the syntax below -:
oms[:order_items].where(:internal_sla => 3..5) results in this query
=> #<Sequel::Mysql2::Dataset: "SELECT * FROM `order_items` WHERE ((`internal_sla` >= 3) AND (`internal_sla` <= 5))">
But how can I change the active record query to give me something like this => select internal_sla from order_items where (internal_sla<=3 and internal_sla>=0) OR (internal_sla<=15 and internal_sla>=10)
.where("(internal_sla >= ? AND internal_sla <= ? OR
internal_sla >= ? AND internal_sla <= ? )", 0, 3, 10, 15).pluck(:internal_sla)
UPDATE after comment:
If internal_sla is integer, you can:
.where(:internal_sla => (0..3).to_a + (10..15).to_a).pluck(:internal_sla)
edit: fixed typo
This works
oms[:order_items].where(:internal_sla => [0..3, 10..15])

Ruby on Rails Demographic Data

I made an site for a PS3 game and I have quite a lot of users. I am wanting to make tournaments based on peoples locations and would also like to target age groups. When users sign up the input there date of birth in the format YYYY-MM-DD. I am pulling the data and making it into a hash like so:
# Site.rb
has_many :members
def ages
ages = {"Under 18" => 0, "19-24" => 0, "25-35" => 0, "36-50" => 0, "51-69" => 0,"70+" => 0}
ages_results = self.members.count("DATE_FORMAT(dob, '%Y')", :group =>"DATE_FORMAT(dob, '%Y')")
ages_results.each do |k,v|
k = k.to_i
if k.between?(18.years.ago.strftime("%Y").to_i, 0.years.ago.strftime("%Y").to_i)
ages["Under 18"] += v
elsif k.between?(24.years.ago.strftime("%Y").to_i, 19.years.ago.strftime("%Y").to_i)
ages["19-24"] += v
elsif k.between?(35.years.ago.strftime("%Y").to_i, 25.years.ago.strftime("%Y").to_i)
ages["25-35"] += v
elsif k.between?(50.years.ago.strftime("%Y").to_i, 36.years.ago.strftime("%Y").to_i)
ages["36-50"] += v
elsif k.between?(69.years.ago.strftime("%Y").to_i, 51.years.ago.strftime("%Y").to_i)
ages["51-69"] += v
elsif k > 70.years.ago.strftime("%Y").to_i
ages["70+"] += v
end
end
ages
end
I am not a expert ruby developer and not sure if the above approach is good or it can be done a much better way, could anyone give me some advice about this?
Cheers
Couple of things to note in your code:
you seem to disregard month and day when a user was born
you convert to and from strings unnecessarilly:
50.years.ago.strftime("%Y").to_i
could be written as
50.years.ago.year
hard-coded values all over the code
I would start rewriting by finding an adequate method for calculating exact age. This one seems to be ok:
require 'date'
def age(dob)
now = Time.now.utc.to_date
now.year - dob.year - ((now.month > dob.month || (now.month == dob.month && now.day >= dob.day)) ? 0 : 1)
end
Then I would extract age table to a separate structure, to be able to change it easily, if needed, and have it visually together:
INF = 1/0.0 # convenient infinity
age_groups = {
(0..18) => 'Under 18',
(19..24) => '19-24',
(25..35) => '25-35',
(36..50) => '36-50',
(51..69) => '51-69',
(70..INF) => '70+'
}
Next you can take as the input the array of users' birth dates:
users_dobs = [Date.new(1978,4,16), Date.new(2001,6,13), Date.new(1980,10,22)]
And starting to find a suitable method to group them based on your map, say using inject:
p users_dobs.each_with_object({}) {|dob, result|
age_group = age_groups.keys.find{|ag| ag === age(dob)}
result[age_group] ||= 0
result[age_group] += 1
}
#=>{25..35=>2, 0..18=>1}
or, perhaps, using group_by
p users_dobs.group_by{|dob|
age_groups.keys.find{|ag| ag === age(dob)}
}.map{|k,v| [age_groups[k], v.count]}
#=>[["25-35", 2], ["Under 18", 1]]
etc.

Rails - Building Conditions for Use in a Query

I usually like to build a conditions hash like this below:
conditions = {}
conditions[:color] = "black"
conditions[:doors] = 4
conditions[:type] = "sedan"
Cars.find(:all, :conditions=>conditions)
But how would I add a date range into this for something like:
year >= '2011-01-01' and year < '2011-02-01'
I am assuming you are using Rails 2.3.x and year is a date column.
conditions = {}
conditions[:color] = "black"
conditions[:doors] = 4
conditions[:type] = "sedan"
# create a date range
conditions[:year] = (Date.parse("2011-01-01")...Date.parse("2011-02-01"))
Car.all(:conditions => conditions)
If you want to do even more complex queries in 2.3.x use the AR Extensions gem.
Read this article for more details.
If you're on Rails 3, why not use AREL?
Cars.where(:color => "black").
where(:doors => 4).
where(:type => "sedan").
where("year >= '2011-01-01'").
where("year < '2011-02-01'")
Btw, don't use :type as a field name. Rails uses this for STI.
On Rails 2.3, I'd just build up conditions as a String instead.
You can build a up query through relations. The query will not be executed until it needs to be evaluated. This is nice for searches where some parameters are optional.
#cars = Cars.where(:color => "black")
#cars = #cars.where(:doors => 4)
#cars = #cars.where("year >= '2011-01-01'")
#cars = #cars.where("year <= '2011-02-01'")
Or you could just merge all that together into one:
Cars.where(["color=? AND doors=? AND year >= ? AND year <= ?", "black", 4, "2011-01-01", "2011-02-01"]
UPDATE:
For Rails < 3
#cars = Cars.scoped(:conditions => {:color => "black"})
#cars = #cars.scoped(:conditions => {:doors => 4})
#cars = #cars.scoped(:conditions => "year >= '2011-01-01'")
#cars = #cars.scoped(:conditions => "year <= '2011-02-01'")
OR
Cars.all(:conditions => ["color=? AND doors=? AND year >= ? AND year <= ?", "black", 4, "2011-01-01", "2011-02-01"]

Resources