Rspec: Check if array includes object which includes property - ruby-on-rails

I have a json array full of objects.
my_array = [{id => 6, name => "bob"},
{id => 5, name => "jim"},
{id => 2, name => "steve"}]
I need to see if the array holds an object which contains an attribute "id" that is set to 5. The "name" attribute is unknown.
How do I do this in rspec?
I know if I had the name attribute I know I could just do:
my_array.should include({:id => 5, :name => "jim"})

expect(myArray.find { |item| item[:id] == 5 }).to_not be_nil
or with the legacy should syntax
myArray.find { |item| item[:id] == 5 }.should_not be_nil
Please note that myArray is not following Ruby conventions. Variables use underscore
my_array
not camelcase
myArray

It is also possible using the having_attributes alias:
expect(my_array).to include( an_object_having_attributes(id: 5) )
or, as in my own use case, matching the whole array:
expect(my_array).to contain_exactly(
an_object_having_attributes(id: 5),
an_object_having_attributes(id: 6),
an_object_having_attributes(id: 2)
)

You can unfold an array and to check matching of two arrays like here:
expect(my_array).to include(*compare_array)
It'll unfold and match each value of array.
It's equivalent to this:
expected([1, 3, 7]).to include(1,3,7)
Source: Relish documentation

This would only be worth it if you were doing many of these, but you could define a custom matcher:
RSpec::Matchers.define :object_with_id do |expected|
match do |actual|
actual[:id] == expected
end
description do
"an object with id '#{expected}'"
end
end
# ...
myArray.should include(object_with_id 5)

Put this any matcher into spec/support/matchers.rb and require it in your spec_helper.rb
RSpec::Matchers.define :any do |matcher|
match do |actual|
actual.any? do |item|
matcher.matches?(item)
end
end
end
Then you can use it in examples like this:
expect(my_array).to any(include(id: 5))

I would use the RSpec 3's composable include matcher like so:
expect(my_array).to include(include(id: 5))
This would have the benefit of a more verbose output via RSpec in case of failure.
it 'expects to have element with id 3' do
my_array = [
{ id: 6, name: "bob" },
{ id: 5, name: "jim" },
{ id: 2, name: "steve" }
]
expect(my_array).to include(include(id: 3))
end
This would generate following failure message:
Failures:
1) Test expects to have element with id
Failure/Error: expect(my_array).to include(include(id: 3))
expected [{:id => 6, :name => "bob"}, {:id => 5, :name => "jim"}, {:id => 2, :name => "steve"}] to include (include {:id => 3})
Diff:
## -1,2 +1,2 ##
-[(include {:id => 3})]
+[{:id=>6, :name=>"bob"}, {:id=>5, :name=>"jim"}, {:id=>2, :name=>"steve"}]
Further reading:
https://relishapp.com/rspec/rspec-expectations/docs/composing-matchers

Here's a customer matcher "include_object" (probably a better name should be used since it just checks if the id's are present)
used as follows
obj = {id:1}
objs = [{id: 1}, {id: 2}, {id: 3}]
expect(objs).to include_object obj
Matcher can handle Object, Hashs (symbols or string)
It also prints just the id's in the array on exception for easier viewing
RSpec::Matchers.define :include_object do |expected|
ids = []
match do |actual|
ids = actual.collect { |item| item['id'] || item[:id] || item.id }
ids.find { |id| id.to_s == expected.id.to_s }
end
failure_message_for_should_not do |actual|
"expected that array with object id's #{ids} would contain the object with id '#{expected.id}'"
end
failure_message_for_should_not do |actual|
"expected that array with object id's #{ids} would not contain the object with id '#{expected.id}'"
end
end

I see there are many responses, but here is an implementation that was helpful for me on a case which you need to check for multiple key-value pairs on each array object, instead of single attribute checking
Usage
expect(array).to include(have_attributes_with_values({ id: 5, name: 'Jim' }))
Matcher implementation
RSpec::Matchers.define :have_attributes_with_values do |expected|
match do |actual|
expected.each do |key, value|
return false unless actual[key] == value
end
end
end

Related

Looking for a rspec matcher that checks a value to be included in array

How can I write this:
Given an array b = ['one', 'two', 'three']
I expect value a to be
included in the array b.
I want to use it together with an all matcher so my final code would look like this:
b = ['one', 'two', 'three']
my_list = [ {'type' => 'one'}, {'type' => 'two'}, {'type' => 'three'} ]
expect(my_list).to all(include("type" => a_value_included_in(b))
Which is testing for:
all hashes from my_list must have a type key whose value is in array b.
Is there such built-in matcher in Rspec?
And how do you check inclusion of a value in the array besides using the obvious reverse: expect([1, 2, 3]).to include(value), which in my example, is not really fitting in?
If you want to check if every string from b is present in any of the hashes from my_list:
b = ['one', 'two', 'three']
my_list = [ {'type' => 'one'}, {'type' => 'two'}, {'type' => 'three'} ]
b.each do |str|
expect(my_list.find { |h| h['type'] == str }).to be_present
end
Or, you can go with:
expect(my_list.map { |h| h['type'] }.sort) to eq(b.sort)
So when someone adds my_list << { 'type' => 'not_in_list' } the spec
will fail.
expect(my_list.map { |h| b.include?(h['type']) }.uniq).to eq(true)
In your case I would simply use (order of elements doesn't matter):
expect(my_list).to match_array(b.map{|v| { 'type' => v })
or use include matcher instead of match_array if b can ba accepted as a subset of my_list.
I don't think that special matcher for such cases exists and/or it's really needed. Still, you can create a custom one if you really can't live w/o it.

Array of hashes where hash is returned by a function in jbuilder

I have a PORO TutorProfileHandler that has a function json that returns a hash.
class TutorProfileHandler
def initialize(opts)
#profile = opts[:tutor_profile]
end
def json
tutor = #profile.tutor
return {
id: tutor.id,
first_name: tutor.first_name,
last_name: tutor.last_name.first + '.',
school: #profile.school,
avatar: #profile.avatar.url,
bio: #profile.bio,
academic_level: #profile.academic_level,
headline: #profile.headline,
major: #profile.major,
rate: #profile.rate,
rating: #profile.rating,
courses: JSON.parse(#profile.courses),
video_url: #profile.video_url
}
end
end
In my index_tutor_profiles.json.jbuilder, I would like to generate
{
tutor_profile: [{id: 1, ...}, {id: 2, ...}, ...],
tutor_sum: 20
}
However when I do this
json.tutor_profiles (#tutor_profiles) do |profile|
TutorProfileHandler.new({tutor_profile: profile}).json
end
json.tutor_sum #tutor_sum
It gives me an empty array for tutor_profiles.
However if I move everything from TutorProfileHandler.json to the jbuilder file, it works. How do I explicitly include the hash returned by TutorProfileHandler.json in the jbuilder array?
Note: This returns an array, but it creates a new key-value pair array:
json.tutor_profiles json.array(#tutor_profiles) do |profile|
TutorProfileHandler.new({tutor_profile: profile}).json
end
Result:
{
array: [{id: 1, ...}, {id: 2, ...}, ...],
tutor_profile: [],
tutor_sum: 20
}
There is a ugly approach:
json.tutor_profiles #tutor_profiles do |profile|
tmp_json = TutorProfileHandler.new({tutor_profile: profile}).json
json.(tmp_json, *(tmp_json.keys))
end
I think the best practise is directly nesting inside model. You can get more information from the its github page.

Merge like array values to new grouped array?

I need to combine like array values as follows:
From:
arr = ['abc', 'abc', 'eff', 'eff', 'foo', 'bar', 'bar', 'bar']
To:
merged_like_arr = [ ['abc', 'abc'], ['eff', 'eff'], ['foo'], ['bar', 'bar', 'bar']]
Basically have an ActiveRecord object which returns a collection of records in order.
aff=Registration.order('affiliate DESC')
aff.map{|code| code. affiliate }
# produces the following:
# arr = ['abc', 'abc', 'eff', 'eff', 'foo', 'bar', 'bar', 'bar']
I need to be alter the data form arr to merged_like_arr (as shown above) so that I can do:
merged_like_arr.sort_by{|arr|-arr.size}.first(10)
#=> [ ["bar", "bar", "bar"], ["eff", "eff"], ["abc", "abc"] ... ]
The purpose is to find the top 10 affiliates in the system by looking up how many times their affiliate id was used in the registration table.
Alternative implementations are also welcome. Thank you!
The purpose is to find the top 10 affiliates in the system by looking up how many times their affiliate id was used in the registration table.
You can use count with group for that kind of query:
If count is used with group, it returns a Hash whose keys represent the aggregated column, and the values are the respective amounts:
Person.group(:city).count
# => { 'Rome' => 5, 'Paris' => 3 }
In your case:
Registration.group(:affiliate).count
#=> { 'abc' => 2, 'eff' => 2, 'foo' => 1, 'bar' => 3 }
This should work:
arr = ['abc', 'abc', 'eff', 'eff', 'foo', 'bar', 'bar', 'bar']
arr = arr.group_by { |e| e }.values
# => [["abc", "abc"], ["eff", "eff"], ["foo"], ["bar", "bar", "bar"]]

JBuilder loop that produces hash

I need loop that produces hash, not an array of objects. I have this:
json.service_issues #service.issues do |issue|
json.set! issue.id, issue.name
end
that results:
service_issues: [
{
3: "Not delivered"
},
{
6: "Broken item"
},
{
1: "Bad color"
},
{
41: "Delivery problem"
}
]
I need this:
service_issues: {
3: "Not delivered",
6: "Broken item",
1: "Bad color",
41: "Delivery problem"
}
Is it possible to do this without converting AR result to hash manually?
Jbuilder dev here.
Short answer: Yes. It's possible without converting array of models into hash.
json.service_issues do
#service.issues.each{ |issue| json.set! issue.id, issue.name }
end
but it'd probably be easier to prepare hash before-hand.
json.service_issues Hash[#service.issues.map{ |issue| [ issue.id, issue.name ] }]
For anyone who is interested in having an hash of arrays (objects), you can use the following code:
#bacon_types.each do |bacon_type|
json.set! bacon_type.name, bacon_type.bacons do |bacon|
bacon.title bacon.title
...
end
You can do it like this way
Jbuilder.encode do |json|
json.service_issues #service.issues.inject({}) { |hash, issue| hash[issue.id] = issue.name; hash }
end
The code generating hash technique may be understood by following example.
[1] pry(main)> array = [{id: 1, content: 'a'}, {id: 2, content: 'b'}]
=> [{:id=>1, :content=>"a"}, {:id=>2, :content=>"b"}]
[2] pry(main)> array.inject({}) { |hash, element| hash[element[:id]] = element[:content]; hash }
=> {1=>"a", 2=>"b"}
The key point of inject to generate hash, return created hash every after inserting new element. Above example, it is realized by ; hash.

Rails - Building a JSON object with multiple items

currently I am creating a JSON object as follows:
#comments = Array.new
comments.collect do |comment|
#comments << {
:id => comment.id,
:content => html_format(comment.content),
:created_at => comment.created_at
}
end
#comments.to_json
And this returns something like this:
[{"created_at":"2011-03-02T09:17:27-08:00","content":"<p>Random.......</p>","id":734}, {"created_at":"2011-03-02T09:17:27-08:00","content":"<p>asdasd.......</p>","id":714}, {"created_at":"2011-03-01T09:17:27-08:00","content":"<p>Random.......</p>","id":134}, {"created_at":"2011-03-01T02:17:27-08:00","content":"<p>dasdasdasdasd.......</p>","id":3124}]
Problem here is that I need to include a few other items that aren't arrays. What I would like is the JSON object to look something like this:
[comments: {"created_at":"2011-03-02T09:17:27-08:00","content":"<p>Random.......</p>","id":734}, {"created_at":"2011-03-02T09:17:27-08:00","content":"<p>asdasd.......</p>","id":714}, {"created_at":"2011-03-01T09:17:27-08:00","content":"<p>Random.......</p>","id":134}, {"created_at":"2011-03-01T02:17:27-08:00","content":"<p>dasdasdasdasd.......</p>","id":3124}, last_load: "123123123123", last_view: "zxczcxzxczxc"]
Any ideas on how I can take what I have above, and expand it to pass additional items other than the comments array?
Thank you!
Add your list to a hash, and then call to_json on the hash.
> a = [1,2,3]
=> [1, 2, 3]
> h = {:comments => a, :foo => "bar"}
=> {:foo=>"bar", :comments=>[1, 2, 3]}
> h.to_json
=> "{\"foo\":\"bar\",\"comments\":[1,2,3]}"

Resources