How to pass a hash in double - ruby-on-rails

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.

Related

Metrics/ParameterLists error in too many method params

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.

Rails: Accept 2D array of strings with strong parameters

We have a Rails controller that gets the following data:
params = ActionController::Parameters.new({
"requests": [{
"params": {
"facets": ["user.id", "user.type"],
"facetFilters": [
["user.type:Individual"]
]
}
}, {
"params": {
"facets": "user.type"
}
}]
})
We want to use strong parameters to accept this data, but I haven't yet seen a pattern that will let us accept the 2D array in facetFilters. I'm tinkering with the following:
params[:requests].each do |request|
request[:permitted] = true
request[:params].each do |o|
if ['facets', 'facetFilters'].include?(o.first)
begin
o[:permitted] = true
rescue
end
end
end
end
As one can see not all attributes are permitted (the permitted attributes don't get passed to children elements), as this returns:
[<ActionController::Parameters {"params"=><ActionController::Parameters {"facets"=>["user.id", "user.type"], "facetFilters"=>[["user.type:Individual"]]} permitted: false>, "permitted"=>true} permitted: false>, <ActionController::Parameters {"params"=><ActionController::Parameters {"facets"=>"user.type"} permitted: false>, "permitted"=>true} permitted: false>]
And there are lots of permitted: false in there...
Is it possible to accomplish this goal? Any pointers would be super helpful!
The best way I've found to get this done is to convert the 2D array to a 1D array, permit the 1D array, and then convert the array back to 2D.
nested_facet_filters = []
(params[:requests] || []).each_with_index do |r, ridx|
if r[:params].key?(:facetFilters) && r[:params][:facetFilters].kind_of?(Array) && r[:params][:facetFilters].first.kind_of?(Array)
# flatten the facet filters into a 1D array because Rails can't permit a 2D array
nested_facet_filters << ridx
r[:params][:facetFilters] = r[:params][:facetFilters].flatten
end
end
permitted = params.permit({
requests: [
:query,
:params => [
:facets => [],
:facetFilters => [],
]
]
}).to_h
# after permitting the params, we need to convert the facetFilters back into a 2D array
if nested_facet_filters.present?
nested_facet_filters.each do |idx|
# create map from facet key to array of values
d = {}
permitted[:requests][idx][:params][:facetFilters].each do |s|
split = s.split(':')
facet = split.first
value = split[1..-1].join(':')
if d.key?(facet)
d[facet] << s
else
d[facet] = [s]
end
end
# mutate facetFilters back to 2D array for posting to Algolia
permitted[:requests][idx][:params][:facetFilters] = d.values
end
end
There's been a merge request to accept 2D arrays for six years open in the Rails issue tracker [thread]...

Stringify/Parse request data through rspec

I'm submitting requests on the frontend where I stringify my data (array of objects) and then parse it in the backend.
When I run my specs, I'm getting the error no implicit conversion of Array into String
How can I stringify my data in my spec so that it's consistent with what I'm doing in the frontend? Or is there another way where I don't have to stringify/parse my data to handle all of this?
This is how my frontend data structure looks like:
"categories_and_years": JSON.stringify(
[
{"category_id": 1, "year_ids":[1, 2, 3]},
{"category_id": 2, "year_ids":[2, 3]},
]
)
In my controller, I'm validating the data is an array first:
def validate_categories_and_years_array
#cats_and_yrs = JSON.parse(params[:categories_and_years])
return unless #cats_and_yrs
if !#cats_and_yrs.is_a?(Array)
render_response(:unprocessable_entity, { description_detailed: "categories_and_years must be an array of objects"})
end
end
In my specs, I'm setting my params like this:
context "when all categories and years are valid" do
let(:params) do
{
school_id: school.id,
id: standard_group.id,
categories_and_years: [
{ category_id: category_1.id, year_ids: [ year_1.id ] }
]
}
end
it "adds standards from specific categories and years to the school" do
post :add, params: params, as: :json
expect(school.achievement_standards).to contain_exactly( std_1 )
end
end
This post explains the difference between a regular ruby hash which you have in your spec and a HashWithIndifferentAccess.
Can you also try to_json?
Params hash keys as symbols vs strings
params = ActiveSupport::HashWithIndifferentAccess.new()
params['school_id'] = school.id
params['id'] = standard_group.id
params['categories_and_years'] = [
{ category_id: category_1.id, year_ids: [ year_1.id ] }
]
params = params.to_json
let(:params) { params }

Sorting by month in an array Ruby on Rails

I need an array that gives me #idea.id sorted by #idea.created_at.month
For example:
[[1,2,3], [4,5,6], [7,8,9], [], [], [], [], [], [], [], [], []]
where ids 1, 2, and 3 have #idea.created_at.month = 1 and so on through month = 12.
#ideas_by_month = Array.new(12){Array.new}
#ideas.each do |idea|
month = idea.created_at.month
#ideas_by_month[month-1] << idea.id
end
By the example, I'd need #ideas_by_month[0] to give me ids 1, 2, 3.
This currently adds all of the ideas into one slot [], and isn't sorting properly. How can I change it to make my array look like the example?
Array.new(12,[]) gives you 12 references to the same array. Array.new(12){Array.new} creates 12 different arrays.
The issue is not in your << call, but in your creation of the #ideas_by_month Array. From the Ruby API...
Array.new(3, true) #=> [true, true, true]
Note that the second argument populates the array with references to
the same object. Therefore, it is only recommended in cases when you
need to instantiate arrays with natively immutable objects such as
Symbols, numbers, true or false.
So, when you're pushing into any of the nested Arrays, it's all referencing the same space in memory. Which is why it appears that every id is getting pushed into every nested Array.
Instead declare your Array with a block:
#ideas_by_month = Array.new(12) { Array.new }
...which would look like this fully implemented as a class method:
idea.rb
class Idea < ActiveRecord::Base
...
def self.ideas_by_month
#ideas_by_month = Array.new(12){Array.new}
Idea.all.each do |idea|
month = idea.created_at.month
#ideas_by_month[month-1] << idea.id
end
return #ideas_by_month
end
...
end

combine two arrays of possibly different sizes into hash

How can I combine these two arrays in to a hash. They may be of equal sizes or not.
#status_array = ["ready", "required", "processing", "approval", "live"]
#part_milestones = [#<Milestone id: 657707, data_type: "ready">, #<Milestone id: 657708, data_type: "required">, #<Milestone id: 657709, data_type: "approval">]
They are sorted already. I just need the hash to deal with the "blanks" properly like so:
{"ready"=>#<Milestone id: 657707, data_type: "ready">, "required"=>#<Milestone id: 657708, data_type: "required">, "processing"=>nil, "approval"=>#<Milestone id: 657709, data_type: "approval">, "live"=>nil}
The cleanest way I know to do this is:
hash = #status_array.inject({}) do |result_hash, status|
result_hash[status] = #part_milestones.select { |milestone| milestone.data_type == status }.first
result_hash
end
You can use zip to merge the arrays into two dimensional and then use the following to convert into hash
Hash[#status_array.zip(#part_milestones)]
Documentation for Hash[]
UPDATE:
just realized that its not a one to one mapping
hash = {}
#status_array.each do |status|
hash[status] = #part_milestone.find{|milestone| milestone.data_type == status}
end
#part_milestones.sort_by &:data_type should do the trick

Resources