I'm trying to create rspec tests to test an array of instances. Specifically, I want to verify certain attributes of each instance within the array. Is there a way to use rspec to test this scenario?
For example, suppose I have the following array that I want to verify:
[#<Car id:1, buy_date: "2022-10-10", model: "Ford">,
#<Car id:2, buy_date: "2021-01-10", model: "Ferrari">,
#<Car id:3, buy_date: "2022-03-12", model: "Toyota">]
As my test, I want to check that the buy_date is correct. I tried the following expect statement but I don't think it's meant for arrays of instances so the tests failed when I expected them to pass.
expect(cars).to include([
have_attributes(
buy_date: "2022-10-10"
),
have_attributes(
buy_date: "2021-01-10"
),
have_attributes(
buy_date: "2022-03-12"
)
])
I've also tried it with match_array instead of include but the result was the same.
Any ideas how to use rspec to accomplish this?
The include matcher is meant to check if the tested collection contains the passed value. So you are checking if any of the elements in the car array is an array, containing 3 objects with the given attributes.
That's because you can have an array of arrays, and you should be able to test if a given array is contained as a value in the array of arrays.
To test multiple values, you have to pass them as multiple arguments:
expect(cars).to include(
have_attributes(
buy_date: "2022-10-10"
),
have_attributes(
buy_date: "2021-01-10"
),
have_attributes(
buy_date: "2022-03-12"
)
)
To improve readability, RSpec offers multiple aliases:
expect(cars).to include(
an_object_having_attributes(
buy_date: "2022-10-10"
),
an_object_having_attributes(
buy_date: "2021-01-10"
),
an_object_having_attributes(
buy_date: "2022-03-12"
)
)
Now the difference whether you will use include in this way, or match_array is whether you want to be permissive, e.g. allow cars to contains other elements as well, or you want it to contain exactly those ones.
Also, in terms of readability, I'd rather combine it with multiple matchers:
expect(cars).to
include(an_object_having_attributes(buy_date: "2022-10-10"))
.and include(an_object_having_attributes(buy_date: "2021-01-10"))
.and include(an_object_having_attributes(buy_date: "2022-03-12"))
Related
I am building a rails 6 app and I am stuck on an issue.
Let's say I have a table tasks which has 3 fields:
id
state
description
The description field is in fact a rich_text field thanks to ActionText.
My problem is that I have a csv file composed of 3 columns (id, state, description) and, during initialization of the app, I want to populate my database with it, using bulk import.
Normally, if description was a normal column, I would do something like this:
Task.insert_all(
# My csv converted in array of hashes [{state: YY, description: ZZZZZZZ}]
)
But as description is not really an attribute of the table tasks, it won't work. How can I still use bulk import to import large set of data, but still use action_text fields?
Right now, I am forced to use "one by one" insertions which takes a very long time!
Thank you for any leads you can bring.
I think the easiest way to do this is by splitting your input like this:
# Tasks
tasks = { id: XX, state: YY, ... }
# Descriptions
descriptions = { record_type: 'Task', record_id: XX, name: 'description', body: 'Actual body' }
Then you can do something like this:
Task.insert_all(tasks)
ActionText::RichText.insert_all(descriptions)
Is this what your are looking for?
========================================================
Update:
Just to clarify how this works, we need to understand that RichText model works as any other model in Rails, but serializing the information as showed here: https://github.com/rails/rails/blob/master/actiontext/app/models/action_text/rich_text.rb#L11
To see what is really being extracted from the database we can use the helper *field*_before_type_cast. For example:
descriptions = [ { record_type: 'Task', record_id: XX, name: 'description', body: '<p>EXAMPLE</p>' } ]
ActionText::RichText.insert_all(descriptions)
ActionText::RichText.last.body => <ActionText::Content....>
ActionText::RichText.last.body_before_type_cast => '<p>EXAMPLE</p>'
Also, keep in mind that you can have one rich text per record (is a 1 - N polymorphic association). So, if you try to insert a second description for your Task it won't work
So I have different factories: factory_1, factory_2
They have different traits trait_1_factory_1, trait_2_factory_1 etc
Is possbile to create factory_1 and factory_2 with specific trairs from the same command, like:
FactoryBot.create(
:factory_1, traits_1_factory_1,
:factory_2,
:factory_2, traits_1_factory2
)
I tried different combinations of () {} [] but doesn't seem to work for me? Is this possbile?
Is this the only solution
Factory.create(:factory_1, traits_1_factory_1)
Factory.create etc.
Thank you
No, it is not possbile to create factory_1 and factory_2 from the same command.
Here, 'factory1' is the Model or class whose objects are created.
FactoryBot.create(:factory1)
If you are doing it for making spec DRY, then rather than doing as above, you can do as below :
[
[:factory_1, traits_1_factory_1],
[:factory_2, traits_1_factory2]
].each do |factory|
FactoryBot.create(*factory)
end
Store multiple factories in array and iterate it.
I believe you want to create objects using different factories here i.e. when you say Factory.create, you mean FactoryBot.create.
Unfortunately, i couldn't find any direct command to accomplish such thing. Here is something using a Hash and a loop.
factories_and_traits = { factory_1: [trait_1_factory_1, trait_2_factory_1],
factory_2: [trait_1_factory_2] }
factories_and_traits.each do |factory, traits|
FactoryBot.create(factory, *traits)
end
I want to create a status attribute for my Taskmodel that would indicate where it is in a three part progress in this order: open => in-progress => complete. It would work in a way similar to how an Amazon package is delivered: ordered => shipped => delivered. I was wondering what would be the best way to setup this attribute. I may be wrong but creating three separate boolean attributes seemed sort've redundant. What's the best way to accomplish this?
Rails 4 has an built in enum macro. It uses a single integer column and maps to a list of keys.
class Order
enum status: [:ordered, :shipped, :delivered]
end
The maps the statuses as so: { ordered: 0, shipped: 1, delivered: 2}
It also creates scopes and "interrogation methods".
order.shipped?
Order.delivered.all
It will also map the enum values when writing queries with hash arguments:
Order.where(status: [:shipped, :delivered])
You should use the aasm gem. It has aasm_states for models, callback functionality etc.
Can anyone tell me the updated way to write this rspec spec? It uses the keyword "items" which has been deprecated and I can't figure out how to properly do it. When I include the matchers gem, I get a conflict about which rspec version to use so I'd just like to avoid its use all together.
This is the spec for counting how many courses I see in the index when I create 4:
expect(courses_response[:name]).to have(4).items
A sample string I am matching is:
{:courses=>[{:courses=>{:id=>1, :name=>"Wilford Rolfson"}}, {:courses=>{:id=>2, :name=>"Mabel Jacobi"}}, {:courses=>{:id=>3, :name=>"Madison Beahan"}}, {:courses=>{:id=>4, :name=>"Miles Corwin"}}]}
I have tried:
expect(courses_response.length).to eq(4)
That yields "1" instead of "4"
These three yield nil:
expect(courses_response[:name]).to eq(4)
expect(courses_response[:id]).to eq(4)
expect(courses_response[:row]).to eq(4)
Thanks for any help you can give!
If I understand it correctly, because your returned object is a hash with a single key (:courses) and value (an array containing the courses), you need to check the length of that array like so:
expect(courses_response[:courses].length).to eq(4)
This is assuming:
courses_response = {:courses=>[{:courses=>{:id=>1, :name=>"Wilford Rolfson"}}, {:courses=>{:id=>2, :name=>"Mabel Jacobi"}}, {:courses=>{:id=>3, :name=>"Madison Beahan"}}, {:courses=>{:id=>4, :name=>"Miles Corwin"}}]}
tl;dr How do I get the corresponding value with the key of an object?
I'm confused why
Atag.where(tag:'brand') gives me what I would call an object for lack of a better term: #<ActiveRecord::Relation [#<Atag id: 1, tag: "brand", created_at: "2015-01-31 04:29:20", updated_at: "2015-01-31 04:29:20">]>
But I'm having the basic difficult of accessing the corresponding value for the key :id.
Atag.where(tag:'brand').id and Atag.where(tag:'brand')[:id] and Atag.where(tag:'brand')(:id) all throw errors, while in this case I'm just trying to have the integer 1 returned.
I seem to be unable to ruby, nor find a succinct answer to this basic question with my google searching skills (or lack there of).
Thanks
From great documentation at the Odin Project.
The key thing to note is that #find returns the actual record while #where returns an ActiveRecord::Relation which basically acts like an array.
So if you're using #where to find a single record, you still need to remember to go into that "array" and grab the first record, e.g. User.where(:email => "foo#bar.com")[0] or User.where(:email => "foo#bar.com").first.
This gets me all the time...
Get the id of your tag = 'brand' with following query:
Atag.find_by(tag:'brand').id
Check following variations:
Atag.find(1)
#gives you the object with the Atag id = 1
Atag.find(100) #let's say this record does not exist then you will
get ActiveRecord::RecordNotFound exception.
Better option :
Atag.where(id: 1)
#this returns you a relation and it's true you are trying to access
only a single object.
Hence, you just need to modify it to :
Atag.where(id: 1).first
#Above one will give you an object of Atag not an association result.
# to verfiy you can execute, Atag.where(id: 1).first.class
Atag.where(id: 999).first
# In this case if there is no record found with id = 999, then it'll
return nil which can be easily handled than an exception found
while using find method.
Get the same flavor using the dynamic finders.
Atag.find_by(id: 1) #gives the Atag with id 1
Atag.find_by_id(1). # same as above.
Atag.find_by(id: 999) #if not found then simply returns nil.
Atag.find_by(name: 'ruby') #return Atag object with name: 'ruby'
Atag.find_by_name('ruby') #same as above.
Yep, looks like you figured it out. For reference, you can use Atag.where(tag:'brand').first to get the first result, and Atag.where(tag:'brand').to_a to get an array of all the matching results.
where return instance of ActiveRecord::Relation which can be treated like an array with records as its members. Even if the result is single it should be accessed like a member of array with single element
Atag.where(tag: 'brand')
returns the array of results and to access id we should get the record from the array first i.e.
Atag.where(tag: 'brand')[0].id
In order to get id of all the matching records we need to use pluck with where. pluck returns an array of attribute that is plucked.
Atag.where(tag: 'brand').pluck(:id)
This would return an array of id from the collection returned by where only.
The find_by method finds the first record matching some conditions. Since find_by returns the record (not an array) , we can do:
Atag.find_by(tag: 'brand').id
PS: No one had mentioned pluck that's why I wrote this answer. Hope its helpful.