Metrics/ParameterLists error in too many method params - ruby-on-rails

I've got an API resource to fetch transaction list. There are few filters and params to make it sortable e.g. per_page, status etc. which may be passed on but only user_id is required. Now my method looks like this:
def list(user_id:, page: nil, per_page: nil, status: nil, payment_id: nil, start_date: nil,
end_date: nil, max_amount: nil)
filters = { start_date:, end_date:, max_amount:, status:, payment_id: }.compact_blank!
params = { filters:, page:, per_page: }.compact_blank!
begin
body = get("users/#{user_id}/transactions", params:).body
rescue Errors::NotFoundError
nil
end
resource_list(body, BaseStruct::Transaction)
end
This code produces me Rubocop error of:
Metrics/ParameterLists: Avoid parameter lists longer than 5 parameters. [8/5]. I know I could get rid of it by # rubocop:disable Metrics/ParameterLists but I don't think that's the best idea. Is it possible to pass those not required field in a different way to avoid that error?

Since you are passing 3 kinds of parameters to the list method, you can group them and pass them to the method like below:
def list(user_id, filter_params, page_params)
filters = filter_params.compact_blank!
params = { filter_params, page_params.slice(:page), page_params.slice(:per_page) }.compact_blank!
begin
body = get("users/#{user_id}/transactions", params).body
rescue Errors::NotFoundError
nil
end
resource_list(body, BaseStruct::Transaction)
end
list(user_id, { status: nil, payment_id: nil, start_date: nil, end_date: nil, max_amount: nil }, { page: nil, per_page: nil })

There is CountKeywordArgs option that I found useful to set false.
My rationale is that kwargs add less complexity than positional or optional parameters. It's more effective replacement of old-school Hash options argument.

Related

How to push/shovel/append active record relation correctly?

I have a fairly complex search form that allows for multiple duplicate nested fields to be submitted at once. I generate unique id's on clone so i can separate them using jquery. I then iterate over the duplicates and do something like the following:
So i have something like:
{..., "search"=>{"searches"=>{}, "searches_0"=>{}...{other_attributes}}
def search_post
search = []
params.each do |k, v|
search << do_search(params, v)
end
end
def do_search(search, v)
search_array = []
search = Model.where() if v[:this_param].present?
search = Model.where() if v[:that_param].present?
# it will be only one of the `search` as this_param or that_param can't be searched
together
search_array << search.where(attr: search[:attr]) if attr.present?
search_array << search.where(attr_2: search[:attr_2]) if attr_2.present?
search_array << search.where(attr_3 search[:attr_3]) if attr_3.present?
search_array.uniq
end
This gives a result like:
[#<ActiveRecord::Relation [#<LineItem id: 15, created_at: "2020-01-03 15:49:19", updated_at: "2020-01-03 15:49:19", ...>]>, #<ActiveRecord::Relation [#<LineItem id: 14, created_at: "2020-01-03 15:49:19", updated_at: "2020-01-03 15:49:19", ...>]>]
I obviously get an array but I need to do more queries on it.
I have tried using search.reduce([], :concat).uniq but this only removes all of the results and only keeps the ActiveRecord::Relation aspect of the array.
What I need is to shovel the results from the loop and be able to use where on it.
How can this be accomplished?
By the looks of it you can try using a query object:
class ModelQuery
def initialize(initial_scope = Model.all)
#initial_scope = initial_scope
end
def call(params)
scoped = by_this_param(initial_scope, params[:this_param])
scoped = by_that_param(initial_scope, params[:that_param])
scoped = by_other_param(initial_scope, params[:other])
# ...
scoped
end
def by_this_param(s, this_param = nil)
this_param ? s.where(this_attr: this_param) : s
end
def by_that_param(s, that_param = nil)
that_param ? s.where(that_attr: that_param) : s
end
# ...
def by_other(s, other = nil)
other ? s.where(other_attr: other) : s
end
end
You can then do things like:
q = ModelQuery.new
# or perhaps...
# q = ModelQuery.new(Model.where(whatever: "however"))
q.call params
q.order(whatever_column: :asc).group(:however)
Obviously you need to adapt and extend the code above to your variable names/parameters. Another upside of using the query object pattern is that it gives you a subtle nudge to structure your parameters coherently so you can pass from view and return from AR quickly, without much fiddling about with the input/output.
Give it a try!

How to pass a hash in double

There is a scenario where I have multiple keys with the same value so to avoid that repetition I have done something like below
sports = [:cricket,:football,:basketball,:baseball,:rugby,:swimming,:table_tennis,:soccer,:karate]
final_hash = Hash.new
sports.each{|d| final_hash[d] = OpenStruct.new(categories: [], count: [], user_hash: {}, sport_count: [], options: {}, period: "",stat_type: "" ) }
Now I want to pass this hash in my double block but Whenever I do so I get an error
context 'For users details Page' do
it 'should give the data' do
###now I want to pass the hash SO can anyone guide me how can I do it
presenter = double(UserPresenter, id: 1, sector_name: nil, final_hash)
end
end
As suggested by #engineersmnky making use of double splat operator(**hash) on hash worked for me.

Create a method to set attributes after initializing an object

I'm initializing a new object and setting the attributes (because there are no attributes for this particular object) before rendering a form like so:
def new
Book.new title: nil, author: nil, genre: nil, language: nil, ect...
end
This to me looks like a code smell.
I'm trying to set the attributes in a method within the model so I can increase readability by using: Book.new.set_attributes. So my set_attributes method in the Book model would look like:
def set_attributes
{posted: nil, company: nil, poster: nil, city: nil, state: nil, title: nil, body: nil, keywords: nil}
end
However this does not work (with or without the {} brackets). Is it possible to call a method after using .new?
Ruby's constructor method is initialize, not new. You shouldn't try to define a method called new. Do something like:
class Book
attr_accessor :title, :author
def initialize(title = nil, author = nil)
#title = title
#author = author
end
end
You don't need to initialize nil values. When calling Book.new, any values that are not provided in a hash (e.g., Book.new(title: 'Bozo', author: 'Clown')) will be nil automatically.

Delete a record if nil is returned

I am making requests to the Facebook API and some of the responses are empty/nil and I am wondering how I can delete these so that when I save them to my model I don't have any nil entries.
def formatted_data
for record in results['data'] do
attrs = {
message: record['message'],
picture: record['picture'],
link: record['link'],
object_id: record['object_id'],
description: record['description'],
created_time: record['created_time']
}
attrs.delete_if { |x| x.nil? }
Post.where(attrs).first_or_create! do |post|
post.attributes = attrs
end
end
As you can see I am trying to use the delete_if method but it's not working.
Here's an example of a response that I would like to delete:
id: 45
message:
picture:
link:
object_id:
large_image_url:
description:
created_time: 2014-04-12 11:38:02.000000000 Z
created_at: 2014-05-01 10:27:00.000000000 Z
updated_at: 2014-05-01 10:27:00.000000000 Z
This kind of record is no good to me as it has no message, so maybe I could make the query specify if message.nil ? then delete
Edit
Been reading the delete_if docs and after iceman's comment, I thought this would work but it doesn't, though it seems closer to what I want:
attrs = attrs.delete_if {|key, value| key = 'message', value = nil }
There are about 25 records returned, of which 5 should be deleted, but after running the above I get one result left in the model:
[#<Post id: 81, message: nil, picture: nil, link: nil, object_id: nil, large_image_url: nil, description: nil, created_time: nil, created_at: "2014-05-01 11:22:40", updated_at: "2014-05-01 11:22:40">]
Why are all the rest being deleted, maybe my syntax for accessing the key is incorrect?
Since #delete_if passes into block two arguments: the key, and value, try this usage:
attrs.delete_if { |k,v| v.nil? }
and for ruby-on-rails you can remove all blank lines, i.e. nil, and empty:
attrs.delete_if { |k,v| v.blank? }
Im adding this in that someone could provide a more efficient way of doing this, maybe before the records get written to the model..But i have managed a solution, albeit a hacky one i would say
I have added this after the creation of the posts
delete_if_nil = Post.where(message: nil)
delete_if_nil.destroy_all
Its another query on the db which isnt ideal i guess
Any other suggestions appreciated

How do I get around a nil object being created in my controller?

So this is my controller for my Home#Index
class HomeController < ApplicationController
def index
#daily_entries = current_user.daily_entries
#weekly_entries = current_user.weekly_entries
#daily_entry = current_user.daily_entries.new
#weekly_entry = current_user.weekly_entries.new
end
end
The reason this is like this is because I am trying to render two form partials for the creation of both the DailyEntry and WeeklyEntry object types on my Home#Index.html.erb.
But, once the page loads, it automatically instantiates an object of each DailyEntry and WeeklyEntry with all nil values. So whenever I do a simple #daily_entries.each loop, it comes upon a record with lots of nil values - even though the record is not nil itself.
Like this:
#<DailyEntry id: nil, breakfast: nil, snack_1: nil, lunch: nil, snack_2: nil, dinner: nil, water_intake: nil, workout: nil, notes: nil, created_at: nil, updated_at: nil, user_id: 1>]
Aside from removing the current_user.daily_entries.new calls, how do I get around these nil objects for my each loops on this page?
Try the "confident code" approach. In this particular case, abuse the methods such as to_s and to_i. Examples work better:
#daily_entries.each do |daily_entry|
# If it's a String, nothing happens, if it's nil, empty string will be returned.
string_val = daily_entry.breakfast.to_s
# If it's a Fixnum, nothing happens, if it's nil, 0 will be returned.
int_val = daily_entry.id.to_i
end
Watch this video with Avdi Grimm, it explains this stuff about confident code really well.
Hope it helps :)
This is what I found works:
In my HomeController.rb:
#daily_entries = current_user.daily_entries.where(:id == true)
That filters out initialized, but unsaved records.

Resources