Rspec object validation failing... unclear on `:foo` vs `"foo"` - ruby-on-rails

I've written a test and it seems so close but not quite right.
it "adds the correct permissions" do
subject
expect(policy.permissions).to have_key("users")
expect(policy.permissions["users"]).to eq({ "all_users" => {
can_view: false,
can_manage: false,
} })
end
Output:
expected: {"all_users"=>{:can_manage=>false, :can_view=>false}}
got: {"all_users"=>{"can_manage"=>false, "can_view"=>false}}
Obviously I'm close but I'm not sure what the deal is in terms of the : notation versus the "got" output from the test itself. How do I get this to pass?

String keys are not equal to symbol keys in Hash class. Here is the example code below:
hash = {}
hash[:a] = 1
hash['b'] = 2
hash[:a] # returns 1
hash['a'] # returns nil
hash[:b] # returns nil
hash['b'] #returns 2
so you should expect the result like this:
expect(policy.permissions["users"]).to eq({ 'all_users' => { 'can_view' => false, 'can_manage' => false, } })

Related

How to compare hash containing array but ignoring order inside the array

In my spec i have the following response structure
something: { foo: [{bar: 'baz'}, {one: 'two'}] }
I try to compare it but the order inside of foo-array is random.
I've found this article:
link, and in it, there is the following code:
Use a_collection_containing_exactly when you have an array, but can’t determine the order of elements
expected = {
"data" => a_collection_containing_exactly(
a_hash_including("id" => "1"),
a_hash_including("id" => "2")
)
}
expect(response.parsed_body).to include(expected)
But it does not work for me as the comparison is seeing matcher a_collection_containing_exactly as part of the hash like
{"base_matcher"=>{"expected"=>[{bar: 'baz'}, {one: 'two'}]}}
What is that I have missed? Is there any better solution?
Edit:
To clarify, here is a minimal reproducible example
expected_value = { something: { foo: [{ bar: 'baz' }, { one: 'two' }] }}
expect(response.parsed_body).to eq(expected_value)
I think the RSpec match_array matcher is what you need:
array_of_hashes = [{bar: "baz"},{one: "two"}]
expect(response.parsed_body[:foo]).to match_array(array_of_hashes)
if your top-level hash has many keys, you can write a custom matcher:
# in spec/matchers/match_sort_indifferent.rb:
require 'rspec/expectations'
RSpec::Matchers.define :match_sort_indifferent do |expected|
match do |actual|
expected.keys.each do |key|
expect(actual[key]).to match_array expected[key]
end
expect(expected.keys.length).to eq actual.keys.length
end
end
# in spec/models/test_spec.rb
require 'rails_helper'
require 'matchers/match_sort_indifferent'
result = {foo: [{bar: "baz"},{qux: "dak"}]}
describe {
it {
expect(result).to match_sort_indifferent({foo: [{qux: "dak"},{bar: "baz"}]})
expect(result).not_to match_sort_indifferent({foo: [{qux: "sum"},{bar: "baz"}]})
}
}
Thanks for the answers. I solved the problem with a help of Rspec-json-expectations gem and UnorderedArray
So my solution looks like this:
expected_value = { something: { foo: UnorderedArray({ bar: 'baz' }, { one: 'two' }) }}
and the matcher
expect(response.parsed_body).to include_json expected_value
The downside is that it only checks if the expected values are present, and doesn't catch the extra ones.

Use Single Quote instead of colons in Ruby/Rails

I'm trying to get this test to pass:
it "returns an object with the correct fields" do
expected_response = {"animal_time": #curr_time.strftime("%F %T"), "animals":{}}
expect(AnimalHaircutSession.latest_for(#animal_haircut)).to eq(expected_response)
end
When running the test I get this error:
expected: {:animal_time=>"2020-02-14 09:48:30", :animals=>{}}
got: {"animal_time"=>"2020-02-14 16:48:30", "animals"=>{}}
Why is it converting my expected response to colons and how can I set my expected response to use single quotes?
In Ruby colons are used to define hashes with symbols for keys . The keys will be cast into symbols.
{ "foo": "bar" } == { :foo => "bar" } # true
{ "foo": "bar" } != { "foo" => "bar" } # true
If you want to define a hash where the keys are not symbols use hash-rockets (=>).
it "returns an object with the correct fields" do
expected_response = {
"animal_time" => #curr_time.strftime("%F %T"),
"animals" => {}
}
expect(AnimalHaircutSession.latest_for(#animal_haircut)).to eq(expected_response)
end
While Ruby will allow you to mix hash-rockets and colons in a hash it's generally not considered good form.

Ruby - Access value from json array

I am creating an array of fields
def create_fields fields
fields_list = []
fields.each do |field|
# puts "adding_field to array: #{field}"
field_def = { field: field, data: { type: 'Text', description: '' } }
fields_list.push field_def
end
fields_list
end
The fields_list is being set to a jsonb field.
Lets say I pass in
create_fields ['Ford', 'BMW', 'Fiat']
Json result is an array:
{"field"=>"Ford", "data"=>{"type"=>"Text", "description"=>""}}
{"field"=>"BMW", "data"=>{"type"=>"Text", "description"=>""}}
{"field"=>"Fiat", "data"=>{"type"=>"Text", "description"=>""}}
How can I access the 'Ford' from the json array? Am i creating the array incorrectly? Is there a better way to create this array so I can access the field i want?
This assertion passes assert_equal(3, fields.count)
However i want to get 'Ford' and check it's properties, e.g. type = 'Text', type could equal 'Number' or whatever.
The result of your create_fields method with the specified parameters is the following:
[
{:field=>"Ford", :data=>{:type=>"Text", :description=>""}},
{:field=>"BMW", :data=>{:type=>"Text", :description=>""}},
{:field=>"Fiat", :data=>{:type=>"Text", :description=>""}}
]
It means that if you want to access the line belonging to "Ford", you need to search for it like:
2.3.1 :019 > arr.select{|e| e[:field] == "Ford" }
=> [{:field=>"Ford", :data=>{:type=>"Text", :description=>""}}]
2.3.1 :020 > arr.select{|e| e[:field] == "Ford" }[0][:data][:type]
=> "Text"
This is not optimal, because you need to search an array O(n) instead of using the pros of a hash. If there are e.g.: 2 "Ford" lines, you'll get an array which contains 2 elements, harder to handle collisions in field value.
It would be better if you created the array like:
def create_fields fields
fields_list = []
fields.each do |field|
# puts "adding_field to array: #{field}"
field_def = [field, { type: 'Text', description: '' } ]
fields_list.push field_def
end
Hash[fields_list]
end
If you choose this version, you can access the members like:
2.3.1 :072 > arr = create_fields ['Ford', 'BMW', 'Fiat']
=> {"Ford"=>{:type=>"Text", :description=>""}, "BMW"=>{:type=>"Text", :description=>""}, "Fiat"=>{:type=>"Text", :description=>""}}
2.3.1 :073 > arr["Ford"]
=> {:type=>"Text", :description=>""}
2.3.1 :074 > arr["Ford"][:type]
=> "Text"
Both of the above examples are Ruby dictionaries / Hashes.
If you want to create a JSON from this, you will need to convert it:
2.3.1 :077 > require 'json'
=> true
2.3.1 :078 > arr.to_json
=> "{\"Ford\":{\"type\":\"Text\",\"description\":\"\"},\"BMW\":{\"type\":\"Text\",\"description\":\"\"},\"Fiat\":{\"type\":\"Text\",\"description\":\"\"}}"
This is a structure that makes more sense to me for accessing values based on known keys:
def create_fields fields
fields_hash = {}
fields.each do |field|
fields_hash[field] = {type: 'Text', description: ''}
end
fields_hash
end
# The hash for fields_hash will look something like this:
{
Ford: {
type: "Text",
description: ""
},
BMW: {...},
Fiat: {...}
}
This will allow you to access the values like so: fields[:Ford][:type] in ruby and fields.Ford.type in JSON. Sounds like it would be easier to return an Object rather than an Array. You can access the values based on the keys more easily this way, and still have the option of looping through the object if you want.
Obviously, there are several ways of creating or accessing your data, but I'd always lean towards the developer picking a data structure best suited for your application.
In your case currently, in order to access the Ford hash, you could use the Ruby Array#detect method as such:
ford = fields_list.detect{|field_hash| field_hash['field'] == 'Ford' }
ford['data'] # => {"type"=>"Text", "description"=>""}
ford['data']['type'] # => 'Text'
So, you have result of your method:
result =
[
{"field"=>"Ford", "data"=>{"type"=>"Text", "description"=>""}},
{"field"=>"BMW", "data"=>{"type"=>"Text", "description"=>""}},
{"field"=>"Fiat", "data"=>{"type"=>"Text", "description"=>""}}
]
to get 'Ford' from it you can use simple method detect
result.detect { |obj| obj['field'] == 'Ford' }
#=> { "field"=>"Ford", "data"=>{"type"=>"Text", "description"=>""}
Also I recommend you to edit your method to make it more readable:
def create_fields(fields)
fields.map do |field|
{
field: field,
data: {
type: 'Text',
description: ''
}
}
end
end

`try` method when trying to fetch hash value

I'm trying to avoid an error message when pulling from a hash which may or may not have a value. I either want it to return the value or return nil.
I thought the try method would do it, but I'm still getting an error.
key not found: "en"
My hash is an hstore column called content... content['en'], etc.
content = {"es"=>"This is an amazing event!!!!!", "pl"=>"Gonna be crap!"}
Try method
#object.content.try(:fetch, 'en') # should return nil, but errors even with try method
I thought this would work but it doesn't. How else can I return a nil instead of an error?
Also, the content field itself might also be nil so calling content['en'] throws:
undefined method `content' for nil:NilClass
If you need to allow for object.content.nil?, then you'd use try. If you want to allow for a missing key then you don't want fetch (as Priti notes), you want the normal [] method. Combining the two yields:
object.content.try(:[], 'en')
Observe:
> h = { :a => :b }
=> {:a=>:b}
> h.try(:[], :a)
=> :b
> h.try(:[], :c)
=> nil
> h = nil
=> nil
> h.try(:[], :a)
=> nil
You could also use object.content.try(:fetch, 'en', nil) if :[] looks like it is mocking you.
See the Hash#fetch
Returns a value from the hash for the given key. If the key can’t be found, there are several options: With no other arguments, it will raise an KeyError exception; if default is given, then that will be returned; if the optional code block is specified, then that will be run and its result returned.
h = { "a" => 100, "b" => 200 }
h.fetch("z")
# ~> -:17:in `fetch': key not found: "z" (KeyError)
So use:
h = { "a" => 100, "b" => 200 }
h.fetch("z",nil)
# => nil
h.fetch("a",nil)
# => 100
Just use normal indexing:
content['en'] #=> nil
As of Ruby 2.0, using try on a possibly nil hash is not neat. You can use NilClass#to_h. And for returning nil when there is no key, that is exactly what [] is for, as opposed to what fetch is for.
#object.content.to_h["en"]

rails rspec, any way to handle multiple 'should' for the same example via a block?

Suppose the model method foo() returns an array [true, false, 'unable to create widget']
Is there a way to write an rspec example that passes that array as a block that verifies [0] = true, [1] = false, and [2] matches a regex like /
Currently, I do it like:
result = p.foo
result[2].should match(/unable/i)
result[0].should == true
result[1].should == false
I can't quite get my head around how that might be doable with a block?
It would be slightly over engineered but try to run this spec with --format documentation. You will see a very nice specdocs for this method ;)
describe '#some_method' do
describe 'result' do
let(:result) { subject.some_method }
subject { result }
it { should be_an_instance_of(Array) }
describe 'first returned value' do
subject { result.first }
it { should be_false }
end
describe 'second returned value' do
subject { result.second }
it { should be_true }
end
describe 'third returned value' do
subject { result.third }
it { should == 'some value' }
end
end
end
Do you mean that your result is an array and you have to iterate over to test it's various cases?
Then, you can do that by following, right:
result = p.foo
result.each_with_index do |value, index|
case index
when 0 then value.should == true
when 1 then value.should == false
when 2 then value.shoud match(/unable/i)
end
end

Resources