How to use `Searchkick.multi_search` along with `with_highlights`? undefined method `with_highlights' - ruby-on-rails

We're trying to optimize our search requests by making a single bulk search, so we're trying to use Searchkick.multi_search. However, it only returns Searchkick::Query with the results populated in a results attribute, as a regular Array.
Then, now if I try results.with_highlights... it fails with
undefined method `with_highlights' for #<Array:0x000055a82a7440f0>
Or if I try on the search_query.with_highlights it fails with
undefined method `with_highlights' for #<Searchkick::Query:0x00007f47c5d0cde8>
How can I get the highlights when using multi_search?

Updated Answer for Searchkick 4.6.1+
Talked to Ankane from Searchkick here https://github.com/ankane/searchkick/pull/1518. He ended up releasing a new version with fixes to this then the original answer here is only valid up to Searchkick version 4.6.0.
For 4.6.1+ just do:
groups = Group.search(query, execute: false)
users = User.search(query, execute: false)
Searchkick.multi_search([groups, users])
highlighted_groups_results = groups.with_highlights(...
...
Original answer for 4.6.0-
Got it!
After diving into the Searchkick codebase and checking the Searchkick::Query implementation, discovered that the execute method is what I need.
def execute
#execute ||= begin
begin
response = execute_search
if retry_misspellings?(response)
prepare
response = execute_search
end
rescue => e # TODO rescue type
handle_error(e)
end
handle_response(response)
end
end
https://github.com/ankane/searchkick/blob/230ec8eb996ae93af4dc7686e02555d995ba1870/lib/searchkick/query.rb#L101
handle_response(response) is exactly what we need for making with_highlights work.
Then my final implementation ended up being something like the following:
groups = Group.search(query, execute: false)
users = User.search(query, execute: false)
Searchkick.multi_search([groups, users])
# execute here won't do any additional requests as it's already cached in an instance variable #execute
highlighted_groups_results = groups.execute.with_highlights(...
...

Related

Guard against invalid page number (0) for will_paginate?

The will_pagify gem will_paginate will throw an error if you send it the param page=0. Is there a better way than declaring page = nil (or 1) if param[:page] =~ /^[0]{,}$/? Hoping this is to support zero-index pages and I can disable this with a function parameter.
*There may be other invalid inputs I haven't tested. Strings go to nil.
Update:
Trying to avoid:
begin
model = Model.where(...).paginate(page: params[:page])
<additional code>
rescue
model = Model.where(...).paginate(page: '1')
<redo additional code again or use function***>
vs.
current_page = clean_page(params[:page])
model = Model.where(...).paginate(page: current_page)
You can leverage what WillPaginate has already done to handle all cases as follows:
def clean_page(page)
begin
WillPaginate::PageNumber(page)
rescue WillPaginate::InvalidPage
1
end
end
and that will handle all the same issues because you are leveraging their validation process and anything that fails defaults to page 1.
Working Example

How to DRY a list of functions in ruby that are differ only by a single line of code?

I have a User model in a ROR application that has multiple methods like this
#getClient() returns an object that knows how to find certain info for a date
#processHeaders() is a function that processes output and updates some values in the database
#refreshToken() is function that is called when an error occurs when requesting data from the object returned by getClient()
def transactions_on_date(date)
if blocked?
# do something
else
begin
output = getClient().transactions(date)
processHeaders(output)
return output
rescue UnauthorizedError => ex
refresh_token()
output = getClient().transactions(date)
process_fitbit_rate_headers(output)
return output
end
end
end
def events_on_date(date)
if blocked?
# do something
else
begin
output = getClient().events(date)
processHeaders(output)
return output
rescue UnauthorizedError => ex
refresh_token()
output = getClient().events(date)
processHeaders(output)
return output
end
end
end
I have several functions in my User class that look exactly the same. The only difference among these functions is the line output = getClient().something(date). Is there a way that I can make this code look cleaner so that I do not have a repetitive list of functions.
The answer is usually passing in a block and doing it functional style:
def handle_blocking(date)
if blocked?
# do something
else
begin
output = yield(date)
processHeaders(output)
output
rescue UnauthorizedError => ex
refresh_token
output = yield(date)
process_fitbit_rate_headers(output)
output
end
end
end
Then you call it this way:
handle_blocking(date) do |date|
getClient.something(date)
end
That allows a lot of customization. The yield call executes the block of code you've supplied and passes in the date argument to it.
The process of DRYing up your code often involves looking for patterns and boiling them down to useful methods like this. Using a functional approach can keep things clean.
Yes, you can use Object#send: getClient().send(:method_name, date).
BTW, getClient is not a proper Ruby method name. It should be get_client.
How about a combination of both answers:
class User
def method_missing sym, *args
m_name = sym.to_s
if m_name.end_with? '_on_date'
prop = m_name.split('_').first.to_sym
handle_blocking(args.first) { getClient().send(prop, args.first) }
else
super(sym, *args)
end
end
def respond_to? sym, private=false
m_name.end_with?('_on_date') || super(sym, private)
end
def handle_blocking date
# see other answer
end
end
Then you can call "transaction_on_date", "events_on_date", "foo_on_date" and it would work.

Spree error when using decorator with the original code

Need a little help over here :-)
I'm trying to extend the Order class using a decorator, but I get an error back, even when I use the exactly same code from source. For example:
order_decorator.rb (the method is exactly like the source, I'm just using a decorator)
Spree::Order.class_eval do
def update_from_params(params, permitted_params, request_env = {})
success = false
#updating_params = params
run_callbacks :updating_from_params do
attributes = #updating_params[:order] ? #updating_params[:order].permit(permitted_params).delete_if { |k,v| v.nil? } : {}
# Set existing card after setting permitted parameters because
# rails would slice parameters containg ruby objects, apparently
existing_card_id = #updating_params[:order] ? #updating_params[:order][:existing_card] : nil
if existing_card_id.present?
credit_card = CreditCard.find existing_card_id
if credit_card.user_id != self.user_id || credit_card.user_id.blank?
raise Core::GatewayError.new Spree.t(:invalid_credit_card)
end
credit_card.verification_value = params[:cvc_confirm] if params[:cvc_confirm].present?
attributes[:payments_attributes].first[:source] = credit_card
attributes[:payments_attributes].first[:payment_method_id] = credit_card.payment_method_id
attributes[:payments_attributes].first.delete :source_attributes
end
if attributes[:payments_attributes]
attributes[:payments_attributes].first[:request_env] = request_env
end
success = self.update_attributes(attributes)
set_shipments_cost if self.shipments.any?
end
#updating_params = nil
success
end
end
When I run this code, spree never finds #updating_params[:order][:existing_card], even when I select an existing card. Because of that, I can never complete the transaction using a pre-existent card and bogus gateway(gives me empty blanks errors instead).
I tried to bind the method in order_decorator.rb using pry and noticed that the [:existing_card] is actuality at #updating_params' level and not at #updating_params[:order]'s level.
When I delete the decorator, the original code just works fine.
Could somebody explain to me what is wrong with my code?
Thanks,
The method you want to redefine is not really the method of the Order class. It is the method that are mixed by Checkout module within the Order class.
You can see it here: https://github.com/spree/spree/blob/master/core/app/models/spree/order/checkout.rb
Try to do what you want this way:
Create file app/models/spree/order/checkout.rb with code
Spree::Order::Checkout.class_eval do
def self.included(klass)
super
klass.class_eval do
def update_from_params(params, permitted_params, request_env = {})
...
...
...
end
end
end
end

How do you access database error information when using Rails and Postgres

I am using find_by_sql to connect to a Postgres database and execute a database function. The database function executes a number of SQL statements and raises exceptions as required.
How do I trap the error code and error message raised by the Postgres function in Rails?
def self.validate_email(systemuserid, emailaddress)
begin
result = (self.find_by_sql(["SELECT fn_systemuser_validate_email(?, ?) AS returncode",
systemuserid, emailaddress])).first
rescue => err
# I want to get access to the error code and error message here and act accordingly
# errno = ??
# errmsg = ??
if errno == 10000
end
end
return result[:returncode]
end
I started by trying to find this information in the connection object - no such luck.
Any help much appreciated.
Currently active record replaces the original error with an internal one without passing on the original with the new error. I cant understand why any one would want this.
So the only solution right now is to monkey patch ;)
module ActiveRecord
module ConnectionAdapters
class AbstractAdapter
def translate_exception(e, message)
ActiveRecord::WrappedDatabaseException.new(message,e)
end
# Replaces
# def translate_exception(e, message)
# # override in derived class
# ActiveRecord::StatementInvalid.new(message)
# end
end
end
end
Now you can get the original_exception.
def self.validate_email(systemuserid, emailaddress)
begin
result = (self.find_by_sql(["SELECT fn_systemuser_validate_email(?, ?) AS returncode", systemuserid, emailaddress])).first
rescue ActiveRecord::WrappedDatabaseException => e
pgerror = e.original_exception
# Exact api depends on PG version, check the docs for your version.
puts "Doing my stuff: #{pgerror.result.result_error_message}"
end
end
This works with pg version 0.11 and Rails 3.0.9. Will probably work with later versions.
I let this one go for a while, (9 months!) but picked it up again due to a new impetus.
I used the monkey patch suggested by Darwin (sorry that the pull request didnt get the vote) and have then discovered that the code I need (with reference to http://deveiate.org/code/pg/PG/Result.html) is as follows:
rescue ActiveRecord::WrappedDatabaseException => e
pgerror = e.original_exception
sqlstate = pgerror.result.error_field(PG::Result::PG_DIAG_SQLSTATE )
end
Just look at .cause.
begin
# whatever.
rescue => err
p err.cause
end
You can user the errors array of your model, like others database:
errmsg = YourModel.errors[0].full_messages

Memcached always miss (rails)

I have a class with this method:
def telecom_info
Rails.cache.fetch("telecom_info_for_#{ref_num}", :expires_in=> 3.hours) do
info = Hash.new(0)
Telecom::SERVICES.each do |source|
results = TelecomUsage.find(:all,
:joins=>[:telecom_invoice=>{ :person=> :org_person}],
:conditions=>"dotted_ids like '%#{ref_num}%' and telecom_usages.ruby_type = '#{source}'",
:select=>"avg(charge) #{source.upcase}_AVG_CHARGE,
max(charge) #{source.upcase}_MAX_CHARGE,
min(charge) #{source.upcase}_MIN_CHARGE,
sum(charge) #{source.upcase}_CHARGE,
avg(volume) #{source.upcase}_AVG_VOLUME,
max(volume) #{source.upcase}_MAX_VOLUME,
min(volume) #{source.upcase}_MIN_VOLUME,
sum(volume) #{source.upcase}_VOLUME
")
results = results.first
['charge', 'volume'].each do |source_type|
info["#{source}_#{source_type}".to_sym] = results.send("#{source}_#{source_type}".downcase).to_i
info["#{source}_min_#{source_type}".to_sym] = results.send("#{source}_min_#{source_type}".downcase).to_i
info["#{source}_max_#{source_type}".to_sym] = results.send("#{source}_max_#{source_type}".downcase).to_i
info["#{source}_avg_#{source_type}".to_sym] = results.send("#{source}_avg_#{source_type}".downcase).to_i
end
end
return info
end
end
As you can see, this is an expensive call, and it is called ALOT for each request so I want to cache it. The problem is that memcached does not seem to work, in the log file, I am getting:
Cache read: telecom_info_for_60000000
Cache miss: telecom_info_for_60000000 ({})
The weird thing is that I know memcached is working since it does cache the results of some other functions I have in another model.
Any suggestions? I am running Rails 2.3.5 on REE 1.8.7
Replace return info with info.
Rails.cache.fetch("telecom_info_for_#{ref_num}", :expires_in=> 3.hours) do
# ...
info
end
The return keyword always returns from the current method, which means that info is never returned to your call to Rails.cache.fetch, nor is the rest of that method ever executed. When the last statement simply is info, this is the value that will be given to Rails.cache.fetch, and you will allow the method to finish its duty by storing this value in the cache.
Compare the following:
def my_method
1.upto(3) do |i|
# Calling return immediately causes Ruby to exit the current method.
return i
end
end
my_method
#=> 1
As a rule of thumb: always omit return unless you really mean to exit the current block and return from the current method.

Resources