Rails ActiveRecord .where method doesn't execute SQL query - ruby-on-rails

I've written a set of Rails search methods that takes an optional parameter to define the rigor of the search. The core method is as follows:
class Participant < ActiveRecord::Base
def self.matches(field_name, param, rigor = 'exact')
where("lower(#{field_name}) like ?", "#{param}") if rigor == 'exact'
where("lower(#{field_name}) like ?", "%#{param}%") if rigor == 'soft'
end
end
I'm getting this result:
[1] pry(main)> >> Participant.matches('last_name', 'Goodman', 'soft')
Participant.matches('last_name', 'Goodman', 'soft')
Participant Load (5.1ms) SELECT "participants".* FROM "participants" WHERE (lower(last_name) like '%Goodman%')
=> [#<Participant:0x007fc6fecee8d0
id: 17,
first_name: "Harris",
last_name: "Goodman",
gender: 0,
birthdate: nil,
city: nil,
state_code: "CA",
email: nil,
phone: nil,
created_at: Wed, 30 Mar 2016 11:18:33 MDT -06:00,
updated_at: Wed, 30 Mar 2016 11:18:33 MDT -06:00,
created_by: 1,
updated_by: 1,
country_code: "US",
user_id: nil>]
[2] pry(main)> >> Participant.matches('last_name', 'Goodman', 'exact')
Participant.matches('last_name', 'Goodman', 'exact')
=> nil
Rails doesn't seem to be firing the SQL query when the 'exact' parameter is passed.
I've also tried = in place of like but get the same result. Worked on this for over an hour with no success. Any ideas would be welcome.
EDIT: OK, so a bit of refactoring solves the problem:
def self.matches(field_name, param)
where("lower(#{field_name}) like ?", "%#{param}%")
end
def self.exact_matches(field_name, param)
where("lower(#{field_name}) like ?", "#{param}")
end
I would still like to know why this works but the more elegant earlier solution does not.

Every function/method returns the last evaluated value.
In your case it's
where("lower(#{field_name}) like ?", "%#{param}%") if rigor == 'soft'
which returns nil if rigor is not 'soft'.
So adding a return should doing what you want:
def self.matches(field_name, param, rigor = 'exact')
return where("lower(#{field_name}) like ?", "#{param}") if rigor == 'exact'
where("lower(#{field_name}) like ?", "%#{param}%") if rigor == 'soft'
end

Related

and sometimes I get failed spices because of Time.now, can't understand why

so i have a method in model
class << self
def last_week
start = Time.zone.now.beginning_of_week - 7.days
finish = start + 7.days
where('appointment_at >= ? AND appointment_at < ?', start, finish).order(appointment_at: :desc)
end
end
And I write spec for this method.
RSpec.describe Appointment, type: :model, vcr: { record: :none } do
let!(:time) { Time.now }
let(:appointment_at) { time }
context '.last_week' do
let!(:scoped_appointment) { create(:appointment, appointment_at: time - 2.days) }
let!(:another_appointment) { create(:appointment, appointment_at: time - 16.days) }
it do
travel_to(time) do
expect(Appointment.last_week).to include(scoped_appointment)
expect(Appointment.last_week).not_to include(another_appointment)
end
end
end
end
And sometime i get failed this spec with error.
expected #<ActiveRecord::Relation []> to include #<Appointment id: 18, lead_id: 27, body: nil, appointment_at: "2019-02-25 00:59:47", google_id: nil, ... "pending", user_id: 22, notify: nil, cc_emails: nil, appointment_minutes: nil, status_message: nil>
Diff:
## -1,2 +1,2 ##
-[#<Appointment id: 18, lead_id: 27, body: nil, appointment_at: "2019-02-25 00:59:47", google_id: nil, created_at: "2019-02-27 00:59:47", updated_at: "2019-02-27 00:59:47", timezone: nil, subject: "Meeting with Lead", address: nil, notification: nil, status: "pending", user_id: 22, notify: nil, cc_emails: nil, appointment_minutes: nil, status_message: nil>]
+[]
I can't understand why?
And I have a suggestion that I should tightly set time
in spec_helper.rb
$now = DateTime.parse('2020-01-01 00:00:01 -0500')
will it be right? and why ?
Your test setup is brittle. It will break depending on the day of the week you run your spec.
The scope in your model returns appointments from the previous week, Monday through Sunday (you are calling beginning_of_week and adding 7 days to it)
So if your tests run on a Wednesday, like in the example you provided, the appointment’s appointment_at field will be set to Monday (since you are calculating it as Time.now - 2.days). That means your scope will not cover that appointment.
I suggest you use a specific time in your setup. Given your current setup, using let(:time) { DateTime.parse('2019-02-25 00:00:00') } should work

how to filter array with date range?

array = [#<Product id: 206, product: "first product", created_at: "2018-05-28 09:50:26", updated_at: "2018-05-28 09:50:26">, #<Product id: 207, product: "second product" ,created_at: "2018-05-28 09:50:46", updated_at: "2018-05-28 09:50:46"]
params[:from_date] = "2018-04-28 09:50:26"
params[:to_date] = "2018-05-28 09:50:46"
I'm filtering the above array with the following params (params[:from_date], params[:to_date]) using the below select statement.
array.select { |product| product.created_at >= params[:from_date] && product.created_at <= params[:to_date] }
I think there are more efficient methods than the above.
Are there any other methods to tackle this issue in a more efficient way?
Have you thought of improving the SQL query? It looks like your array contains the Products that are result from a query like Product.all. Maybe you can change that to something like:
Product.where("created_at >= :from_date AND created_at <= :to_date",
{from_date: params[:from_date], to_date: params[:to_date]})
I think that is more efficient approach because you will improve your app performance. And from my point of view the code looks more understandable this way.

Input custom value from parameters to Rails SQL Query

I have written so code to query lists of users who go jogging at a certain time and in certain locations, but I am getting the following error: ActiveRecord::StatementInvalid: SQLite3::SQLException: near "'jogging'"
Does this mean I cannot write a string into that variable? Are there any solutions to this?
users = User.where('ids in (:user_ids)', user_ids:
Activity.where('title :title AND created_at >= :created_at AND location_id: location_id',
{title: 'jogging', created_at: Time.zone.now.beginning_of_day, location_id:
Location.where('id in id', id: user.activities.where.not(location_id: nil)
.order('created_at DESC').first)}))
You can simplified your query this way
location_id = Location.where(
id: user.activities
.where.not(location_id: nil)
.order('created_at DESC').first
)
user_ids = Activity.where("title = ? AND created_at > ? AND location_id = ?",
"jogging", Time.zone.now.beginning_of_day, location_id)
users = User.where(id: user_ids)
But If you want to keep query one liner. You can use this
User.where('id IN(:user_ids)',
user_ids: Activity.where('title = :title AND created_at >= :created_at AND location_id = :location_id', title: 'jogging', created_at: Time.zone.now.beginning_of_day,
location_id: Location.where('id IN(:id)', id: user.activities.where.not(location_id: nil)
.order('created_at DESC').first).ids)
)
Note: I am assuming that you have user object to use above one liner query
Hope this will help you.
I think you forgot to add an = in the query:
Your query:
Activity.where('title :title ...
What you want:
Activity.where('title = :title ...
And if you don't need an operator like > or <, you can also use:
Activity.where(title: title)
If you then need to chain it, it's pretty simple:
Activity.where(title: title).where('foo < ?', 100)

ActiveRecord get average from group

I am trying to write this query in ActiveRecord 4, but to no avail.
SELECT date, AVG(gain) AS avg_gain FROM counters WHERE ( date > '2014-03-03' ) GROUP BY date ORDER BY date DESC;
So I scrambled this together:
Counter.select("date, AVG(gain) as avg_gain").where("date > '2014-03-03'").group(:date).order(date: :desc)
=> #<ActiveRecord::Relation [#<Counter id: nil, date: "2014-04-01">, #<Counter id: nil, date: "2014-03-31">, #<Counter id: nil, date: "2014-03-30">, #<Counter id: nil, date: "2014-03-29">, #<Counter id: nil, date: "2014-03-28">, #<Counter id: nil, date: "2014-03-27">, #<Counter id: nil, date: "2014-03-26">, #<Counter id: nil, date: "2014-03-25">, #<Counter id: nil, date: "2014-03-24">, #<Counter id: nil, date: "2014-03-23">, ...]>
The only trouble is, the result does not contain avg_gain columns. Any ideas?
When you use select, the selected fields are added to the instances in the returned Relation. The returned result does not include those selected fields in the printed output.
Access these fields just as you would access a column attribute:
result = Counter.select("date, AVG(gain) as avg_gain").where("date > '2014-03-03'").group(:date).order(date: :desc)
result.first.avg_gain # Prints expected value for the first Counter
OP wants to see hash values for the items queried. Here are two options, but you should just use the first, the second is here as more of a warning or option when performance doesn't matter:
I'd highly recommend this option, due to performance reasons:
Counter.
where("date > '2014-03-03'").
group(:date).
order(date: :desc).
pluck("date, AVG(gain) as avg_gain").
map { |date, avg_gain| {date: date, avg_gain: avg_gain} }
The solution below looks nice but is about 10x slower. This is because as_json will instantiate a model object for every result:
The Active Model JSON Serializer method, as_json will also return an array of hashes. By default, each result will also include "id"=>nil if you don't use the only: except: option.
Counter.
select("date, AVG(gain) as avg_gain").
where("date > '2014-03-03'").
group(:date).
order(date: :desc).
as_json(except: [:id])

Rails model logic giving wrong result

When i do some logic on a colum value after copied to some variable, my actual colum value on the object is getting changed, my model methods are,
def copy_configuration_values
element_positions_dup = element_positions.dup
cert_element.read_attribute(:field_names)["configuration_values"].each { |k, v|
element_positions_dup["configuration_values"][k] = v if configuration_value_present?(k)
}
element_positions_dup
end
def configuration_value_present?(configuration)
element_positions["configuration_values"] && element_positions["configuration_values"][configuration]
end
And when i call this method from console like below,
1.9.3p194 :001 > t = CertTemplate.find 30
CertTemplate Load (0.3ms) SELECT `cert_templates`.* FROM `cert_templates` WHERE `cert_templates`.`id` = 30 LIMIT 1
=> #<CertTemplate id: 30, name: "aaaaaaaaaaaq", cert_element_id: 22, element_positions: {"configuration_values"=>{"Program"=>"9.523810492621529,24.627720154437824"}, "custom_fields"=>{"college"=>"22.64550296843998,15.349369638973906", "code"=>"16.790123349144345,15.463920671915272"}, "custom_fields_for_rows"=>{"subject name"=>"30.68783230251736,16.609393247624034"}}, created_at: "2012-08-08 07:18:33", updated_at: "2012-08-16 08:03:52", image_file_name: "Marksheet_Updated.jpg", image_content_type: "image/jpeg", image_file_size: 2236497, image_updated_at: "2012-08-08 07:18:33">
1.9.3p194 :002 >
1.9.3p194 :003 > t.copy_configuration_values
CertElement Load (0.2ms) SELECT `cert_elements`.* FROM `cert_elements` WHERE `cert_elements`.`id` = 22 LIMIT 1
=> {"configuration_values"=>{"Program"=>"2"}, "custom_fields"=>{"college"=>"22.64550296843998,15.349369638973906", "code"=>"16.790123349144345,15.463920671915272"}, "custom_fields_for_rows"=>{"subject name"=>"30.68783230251736,16.609393247624034"}}
1.9.3p194 :004 >
1.9.3p194 :005 > t
=> #<CertTemplate id: 30, name: "aaaaaaaaaaaq", cert_element_id: 22, element_positions: {"configuration_values"=>{"Program"=>"2"}, "custom_fields"=>{"college"=>"22.64550296843998,15.349369638973906", "code"=>"16.790123349144345,15.463920671915272"}, "custom_fields_for_rows"=>{"subject name"=>"30.68783230251736,16.609393247624034"}}, created_at: "2012-08-08 07:18:33", updated_at: "2012-08-16 08:03:52", image_file_name: "Marksheet_Updated.jpg", image_content_type: "image/jpeg", image_file_size: 2236497, image_updated_at: "2012-08-08 07:18:33">
1.9.3p194 :006 >
My actual column value is getting changed, what i am doing wrong. Advance thanks.
It looks like the problem is you are assigning references to nested hashes when you iterate through the key values, instead of copies.
Like specifically, "custom_fields" key in both element_positions and element_positions_dup will point to the same Hash object as the value because you assign it without duplicating it. To fix it try
...
element_positions_dup["configuration_values"][k] = v.dup if configuration_value_present?(k)
...
Edit: yeah you need deep copies
Use Marshal serialization

Resources