Rspec send params to subject - ruby-on-rails

I wrote a small service, which need params: [:search][:area], [:search][:floor] etc.
I write test:
subject { AwesomeService.new(params) }
let(:params) do
{
"search" => {
'area' => object1.area,
'floor' => object1.floor
}
}
end
But my test fails(manually work perfectly). When I debug my service in test mode, params[:search][:floor] is NULL. How can I fix my params in test?

The params object in rails does not care if you look for values in it as symbols or strings:
params[:search][:floor] == params['search']['floor']
A Hash in ruby, though, is different - if you insert strings as keys, you need to query it with strings.
param_hash[:search][:floor] != params['search']['floor']
You stub params as a hash. This means you should either set it with symbols instead of strings, or use HashWithIndifferentAccess.
subject { AwesomeService.new(params) }
let(:params) do
ActiveSupport::HashWithIndifferentAccess.new {
"search" => {
'area' => object1.area,
'floor' => object1.floor
}
}
end

Related

Sort items in a nested hash by their key

I have a nested hash with unsorted keys:
given = {
"lorem" => {
:AA => "foo",
:GR => "foo",
:BB => "foo"
},
"ipsum" => {
:ZZ => "foo",
:GR => "foo",
}
}
What I'm trying to accomplish is a hash with sorted keys:
goal = {
"ipsum" => {
:GR => "foo",
:ZZ => "foo"
},
"lorem" => {
:AA => "foo",
:BB => "foo",
:GR => "foo"
}
}
I have experimented with .each method and sort_by
given.each { |topic| topic[:key].sort_by { |k, v| k } }
But I'm getting an error message: TypeError: no implicit conversion of Symbol into Integer
Any help is greatly appreciated!
PS: I noticed with gem pry the output is already sorted. But in IRB it's not.
You can use group_by, and transform_values to transform the values inside each hash, also using sort_by plus to_h:
given.transform_values { |value| value.sort.to_h }.sort.to_h
# {"ipsum"=>{:GR=>"foo", :ZZ=>"foo"}, "lorem"=>{:AA=>"foo", :BB=>"foo", :GR=>"foo"}}
You're getting an error because when iterating over a hash, you have to local variables within the block scope to use, the key and its value, you're assigning only one (topic) and trying to get its key, which would be trying to access a key in:
["lorem", {:AA=>"foo", :GR=>"foo", :BB=>"foo"}]
Which isn't possible as is an array. You can update your code to:
given.each do |topic, value|
...
end
But anyway you'll need a way to store the changes or updated and sorted version of that topic values.
given_hash = {"lorem"=>{:AA=>"foo", :GR=>"foo", :BB=>"foo"}, "ipsum"=>{:ZZ=>"foo", :GR=>"foo"}}
Get keys
given_hash.keys
=> ["lorem", "ipsum"]
New sorted hash
new_hash = {}
given_hash.keys.sort.each do |sorted_key|
new_hash[sorted_key] = given[sorted_key]
end
=> {"ipsum"=>{:ZZ=>"foo", :GR=>"foo"}, "lorem"=>{:AA=>"foo", :GR=>"foo", :BB=>"foo"}}
There can be a better way to do this.

RSpec `post` keyword arguments add unwanted key to params

When testing the handling of a callback, the post should return the contents of the body.
I've tried setting it up using the required keyword arguments:
post '/api/v1/callbacks`, body: { foo: 'bar' }
I want the params to be { foo: 'bar' } but they're being returned as { body: { foo: 'bar' } }.
Is there a way I can use the keyword args while not adding the unwanted body key to the params?
There in no body argument, only params
post '/api/v1/callbacks', params: { foo: 'bar' }
In case you want to pass a whole hash as params, instead of having to specify each value, you can do:
post '/post_path', params_hash.merge({:format => 'json'})
See here for more details: https://stackoverflow.com/a/70396862/7724157

Ruby & fetching hash values magic

I'm trying to parse out JSON data and create my own dictionary to show a subset of the data. The thing is, I'm noticing that my input data changes based on what is scanned (with nmap). Some elements might be an array value, whereas some might not. The combinations seem to be pretty broad.
For instance, here is the simplest input where only an IP address was found:
{
'host' => {
'address' => {
'addr' => '192.168.0.1'
},
'status' => {...}
}
}
But then, the IP and MAC address might be found:
{
'host' => {
'address' => [{
'addrtype' => 'ipv4',
'addr' => '192.168.0.1',
},{
'addrtype' => 'mac',
'mac' => '00:AA:BB:CC:DD:EE',
},
'status' => {...}
}]
}
Those are just a couple examples. Other variations I've seen:
`host.class` = Array
`address.class` = Hash
`host['status'].class` = Array
etc...
As I go through to parse the output, I am first checking if the element is an Array, if it is, I access the key/values one way, whereas if it's not an array, I essentially have to duplicate my code with a few tweaks to it, which doesn't seem very eloquent:
hash = {}
if hosts.class == Array
hosts.each do |host|
ip = if host['address'].class == Array
host['address'][0]['addr']
else
host['address']['addr']
end
hash[ip] = {}
end
else
ip = if hosts['address'].class == Array
hosts['address'][0]['addr']
else
hosts['address']['addr']
end
hash[ip] = {}
end
puts hash
end
In the end, I'm just trying to find a better/eloquent way to produce a hash like below, while accounts for the possibility that an element may/may not be an Array:
{
'192.168.0.1' => {
'mac' => '00:aa:bb:cc:dd:ee',
'vendor' => 'Apple',
'ports' => {
'80' => {
'status' => 'open',
'service' => 'httpd'
}
'443' => {
'status' => 'filtered',
'service' => 'httpd'
}
}
},
192.168.0.2 => {
...
}
}
If there a ruby method that I haven't run across yet that will make this more fluid?
Not really... but you can make it always an array eg by doing something like:
hosts = [hosts] unless hosts.is_a?(Array)
or similar... then just pass that to your now-non-duplicated code. :)
The 20 lines of code in your question can be reduced to a single line using Array#wrap instead of conditionals, and using Enumerable#map instead of Enumerable#each:
Array.wrap(hosts).map { |host| [Array.wrap(host['address']).first['addr'], {}] }.to_h
Now that's magic!

Rspec expect a yield

I have a hash in Rails and one of the values is a yield.
{
key1: 'foo',
key2: lambda { |p| root_path(p) },
}
I am not entirely sure how I can expect key2 to receive this yield
I looked in to yield_control but I am not sure that is what I need.
I want to assert that key2 receives a yield and I also want to assert what that yield returns (root_path(p))
You want to be sure that key2 contains a lambda and you want to test what the lambda returns?
example of a test for a lambda
let(:my_hash) = {
{
key1: 'foo',
key2: lambda { |p| root_path(p) }
}
}
expect(my_hash[:key2].try(:lambda?)).to be true
to test for result
expect(my_hash[:key2].call("foo")) to eq root_path("foo")

Passing hashes into create action in Ruby on Rails

I'm trying to create a workout routine that gets created when a workout gets created by passing this in via ajax:
Parameters: {"utf8"=>"✓", "workout"=>{"name"=>"cool workout", "desc"=>"super cool"}, "exerciseorder"=>["4", "2"], "repssets"=>{"0"=>["40", "4"], "1"=>["60", "6"]}}
Here is what my Create action looks like in my Workout Controller:
exercise_order = params[:exerciseorder]
repssets = params[:repssets]
#workout = Workout.new(workout_params)
if #workout.save
WorkoutRoutine.create(
[
exercise_order.each_with_index.map { |x,i|
{
:exercise_id => x,
:position => i,
:workout_id => #workout.id
}
},
repssets.map { |x| x.last }.each { |y|
{
:reps => y.first,
:sets => y.last
}
}
]
)
render :nothing => true
else
render json: #workout.errors.full_messages, status: :unprocessable_entity
end
If I use an opening and closing '[]' within the WorkoutRoutine.create, it tells me:
ArgumentError: When assigning attributes, you must pass a hash as an argument.
And when I change them to '{}' it tells me:
syntax error, unexpected ',', expecting =>
I've tried a myriad of different combinations and work-arounds but can't seem to figure out why it won't correctly parse the data and save it to the database, any help is very appreciated.
EDIT:
When I remove the initial {} and [] from the WorkoutRoutine.create:
WorkoutRoutine.create(
exercise_order.each_with_index.map { |x,i|
{
:exercise_id => x,
:position => i,
:workout_id => 20
}
},
repssets.map { |x| x.last }.each { |y|
{
:reps => y.first,
:sets => y.last
}
}
)
I get this error message:
ArgumentError: wrong number of arguments (2 for 0..1)
Edit2:
This is the jQuery code that sents to the data field via ajax:
var getId = $(".exercise-list").sortable('toArray');
ary = []
$(getId).each(function () {
id = $(this[0]).selector;
var reps = $("#reps" + id).val();
var sets = $("#sets" + id).val();
ary.push([reps, sets])
});
var orderRepsSets = { exerciseorder: getId, repssets: ary }
var formData = $('#new_workout').serialize();
var data = formData + '&' + $.param(orderRepsSets);
$.ajax({
url: $("#new_workout").attr('action'),
method: 'POST',
data: data,
success: (function() {
....
});
Did I get it correctly that you want to create multiple WorkloadRoutine objects, one for each exercise with the corresponding repetitions, the position, etc. If yes, then in order to do that you will have to pass an array of hashes (one hash for each object) to the WorkoutRoutine.create() function. As engineersmnky correctly stated in his comment, the data structure you are currently passing is more like [[{...},{...},{...}],[{...},{...},{...}]], but instead it should be just [{...},{...},...]. Do achieve that, something like this should do the trick:
WorkoutRoutine.create(
exercise_order.map.with_index { |x,i|
{
:exercise_id => x,
:position => i,
:workout_id => #workout.id,
:reps => repssets[i.to_s].first,
:sets => repssets[i.to_s].last
}
}
)
If you could change repssets to an array like exercise_order you could even remove the string cast for getting the reps and sets, which would simplify the whole think even more.
If it comes for errors they are quite self explanatory. But let's start from beginning..
I assume that WorkoutRoutine is an ActiveRecord::Base model. The WorkoutRoutine.create method gets 0 or 1 argument which should be a Hash or a block.
In the first iteration you were passing an Array instead of Hash, so it looked like:
WorkoutRoutine.create([a, b]) # => ArgumentError: When assigning attributes, you must pass a hash as an argument.
On the second iteration you stripped away the square brackets, what gave you 2 arguments instead of one Hash:
WorkoutRoutine.create(a, b) # => ArgumentError: wrong number of arguments (2 for 0..1)
If you read errors carefully you will start getting the idea what's happening.
About the workout routine itself
From what you specified I would assume that you want something like:
Workout has many Routines
Routine belongs to Workout and Exercise
Routine is composed of fields like
position/order,
number of repetitions,
number of sets
If my assumption is correct, then you want to use nested_attributes and then have parameters and controller like
# given parameters as workout_params
{
workout: {
name: "workout name",
desc: "workout description",
routines_attributes: [
{ position: 1, exercise_id: 4, repetitions_number: 40, sets_number: 4 },
{ position: 2, exercise_id: 2, repetitions_number: 60, sets_number: 6 }
]
}
}
# Controller
def create
#workout = Workout.new workout_params
if #workout.save
redirect_to # ...
else
render :new
end
end
private
def workout_params
params.require(:workout).permit(:name, :desc, routines_attributes: [:position, :exercise_id, :repetitions_number, :sets_number])
end
It should be strait forward how to then create a view with fields_for and update models with proper associations

Resources