Guard against invalid page number (0) for will_paginate? - ruby-on-rails

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

Related

How to use `Searchkick.multi_search` along with `with_highlights`? undefined method `with_highlights'

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(...
...

why no implicit conversion of nil into Hash?

after setup a search into a serializer!
Rails spits out
no implicit conversion of nil into Hash
So, please someone can point out whats wrong with this code?
class SearchController < ApplicationController
def results
results_query = PgSearch.multisearch(params[:q]).paginate(page: page, per_page: 20)
result = results_query.map(&:searchable).map do |result_item|
case result_item.class.name
when 'Post'
PostSerializer.new(result_item)
else
raise NotImplementedError
end
end
render json: {
items: result,
page: page,
pages: results_query.total_pages
}
end
def page
params[:page] || 1
end
def serialize(data, serializer)
ActiveModel::Serializer::CollectionSerializer.new(data, each_serializer: serializer)
end
end
Since your case statement isn't checking many values, you could always make it into a standard if/else statement:
if result_item && result.class.name == 'Post'
PostSerializer.new(result_item)
else
raise NotImplementedError
end
Well, on the screenshots you've provided we can see the log message specifies that the error is on line 5.
According to your code, line 5 is: case result_item.class.name
The error message is TypeError (no implicit conversion of nil into Hash).
You're trying to get the class then the name of result_item. So the problem is with result_item which is equal to nil.
In order the resolve your problem you might want to check the ouput of results_query.map(&:searchable).map.
Based on the screenshot you've provided, I've quickly checked the source code. The offending line seems to be this one: https://github.com/Casecommons/pg_search/blob/master/lib/pg_search/document.rb#L22. The only reason why this would raise the described TypeError is if PgSearch.multisearch_options is nil – which, as far as I understand the code, would only be possible if you accidentally overwrote it in a wrong way. So I'd suggest doublechecking your global setup for PgSearch.multisearch_options to make sure this is actually set.
The east way to check the setting is by using a debugger or putting something like puts PgSearch.multisearch_options or Rails.logger.info 'PgSearch.multisearch_options' into the controller directly above the call that's failing.

Ruby on Rails beginner question : equality

I'm starting to know ROR and I was doing a kind of blog with articles, etc...
I did this code :
def show
id = params[:id]
list = Article.all
is_valid = false
list.all.each do |article|
if article.id == id
#is_valid = true
break
end
end
As you can see, this code just wants to check if the article ID exists or not. So I'm testing equality between id and article.id (which's a model linked to the appropriated table in the database) BUT when I try to use or display #is_valid boolean I saw that article.id == id is FALSE every time, even if article.id = 2 and id = 2. I tried to think about everything that can make this occuring, but I admit I still misunderstand this.
Then I ask you if you know why this is occuring. Of course, an equality like 2 == 2 will change #is_valid to true.
Thank you for your help !
Maybe its because params[:id] it's a string and article.id it's an Integer
(byebug) params
{"controller"=>"admin/my_controller", "action"=>"edit", "id"=>"1"}
And yes it is... "id" is a string "1", so you may try this:
def show
id = params[:id].to_i
list = Article.all
is_valid = false
list.all.each do |article|
if article.id == id
#is_valid = true
break
end
end
end
And maybe could work.
This is the answer to your question,
But if you want to learn a little more about Activerecord you can do this
Article.exists?(params[:id])
and that will do what you are trying to do just with a query against db.
and if you want to get just a simple article
record = Article.find_by(id: params[:id]) #return nil when not exist
if record # if nil will threat like false on ruby
#my code when exist
else
#my code when not exist
end
will work (you also can use find but find will throw an exception ActiveRecord::RecordNotFound when not exists so you have to catch that exception.
Activerecord has many ways to check this you dont need to do it by hand.
def show
#article = Article.find(params[:id])
end
This will create a database query which returns a single row. .find raises a ActiveRecord::NotFound exception if the record is not found. Rails catches this error and shows a 404 page. Article.find_by(id: params[:id]) is the "safe" alternative that does not raise.
Your code is problematic since list = Article.all will load all the records out of the database which is slow and will exhaust the memory on the server if you have enough articles. Its the least effective way possible to solve the task.
If you want to just test for existence use .exists? or .any?. This creates a COUNT query instead of selecting the rows.
Article.where(title: 'Hello World').exists?

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.

Returning true or error message in Ruby

I'm wondering if writing functions like this is considered good or bad form.
def test(x)
if x == 1
return true
else
return "Error: x is not equal to one."
end
end
And then to use it we do something like this:
result = test(1)
if result != true
puts result
end
result = test(2)
if result != true
puts result
end
Which just displays the error message for the second call to test.
I'm considering doing this because in a rails project I'm working on inside my controller code I make calls to a model's instance methods and if something goes wrong I want the model to return the error message to the controller and the controller takes that error message and puts it in the flash and redirects. Kinda like this
def create
#item = Item.new(params[:item])
if !#item.nil?
result = #item.save_image(params[:attachment][:file])
if result != true
flash[:notice] = result
redirect_to(new_item_url) and return
end
#and so on...
That way I'm not constructing the error messages in the controller, merely passing them along, because I really don't want the controller to be concerned with what the save_image method itself does just whether or not it worked.
It makes sense to me, but I'm curious as to whether or not this is considered a good or bad way of writing methods. Keep in mind I'm asking this in the most general sense pertaining mostly to ruby, it just happens that I'm doing this in a rails project, the actual logic of the controller really isn't my concern.
I would say that methods that return different types (e.g. boolean vs. string vs. numbers) under different circumstances are a bad practice.
If you have some sort of test method that wants to return details of why the test has not passed then you can return a pair of values (an Array) as follows:
def test(x)
if x == 1
return true, "x is fine"
else
return false, "Error: x is not equal to one."
end
end
and then write the section of your controller code as:
valid, message = #item.save_image(params[:attachment][:file])
if !valid
flash[:notice] = message
redirect_to(new_item_url) and return
end
If you're talking about a save_image method that will succeed the majority of the time but may fail and you want to indicate this failure and the reason then I would use exceptions e.g.
def save_image(file)
raise "No file was specified for saving" if file.nil?
# carry on trying to save image
end
and then your controller code would be along the lines of:
begin
result = #item.save_image(params[:attachment][:file])
rescue Exception => ex
flash[:notice] = ex.message
redirect_to(new_item_url) and return
end

Resources