Creating a FactoryBot Hash model with complicated structure, including multiple nested attributes - ruby

I'm implementing a set of Cucumber driven API tests and stumbled into one specific model that has a few nested elements, like this:
factory :human_being, class: Hash do
human {
name {
first_name { Faker::Name.first_name }
last_name { Faker::Name.last_name }
}
age { rand(1..100) }
}
initialize_with { attributes }
end
The result I wanted to achieve should look like this:
{
:human => {
:name => {
:first_name => "mike",
:last_name => "blob"
},
:age => 16
}
}
I am invoking creation using FactoryBot.build(:human_being) and I get undefined method 'name' for #<FactoryBot::SyntaxRunner:0x00007fd640a39a80>
My env.rb has the World(FactoryBot::Syntax::Methods) / FactoryBot.find_definitions lines.
I've lurked through a few answers regarding the nested attributes / associations / traits but I didn't find a proper way to get what I want. Sorry if the post is duplicated and thanks in advance for help.

Okay, seems that I've found the solution right after posting the question. Currently the following model creates the Hash I described in the question:
factory :human_being, class: Hash do
human { {
name: {
first_name: Faker::Name.first_name,
last_name: Faker::Name.last_name,
},
age: rand(1..100),
sex: %w(male female other).sample
} }
initialize_with { attributes }
end
Although I'm not sure it is a completely correct answer from guidelines perspective, but at least it works for my exact case.
source (See 'Defining factories'): Because of the block syntax in Ruby, defining attributes as Hashes (for serialized/JSON columns, for example) requires two sets of curly brackets:
factory :program do
configuration { { auto_resolve: false, auto_define: true } }
end

Related

FactoryBot creates records even when a record is provided

I use factorybot to create records in development sometimes. However, it's creating a bunch of extra data that I wasn't expecting.
I have two factories:
FactoryBot.define do
factory :user do
first_name { Faker::Name.first_name }
last_name { Faker::Name.last_name }
email { "#{first_name}.#{last_name}#example.com" }
end
factory :post do
user { create(:user) }
title { Faker::Lorem.sentence }
end
end
If I run FactoryBot.create(:post) in the rails console, it will create a new post with a new user. What I didn't expect is that if I do FactoryBot.create(:post, user: User.first), it would create a post associated with the first user, but still create a new record. So, I get this:
irb(main):001:0> User.count
=> 1
irb(main:002:0> FactoryBot.create(:post, user: User.first)
=> #<Post id: 1, title: 'Lorem Ipsum', host_id: 1>
irb(main:003:0> User.count
=> 2
Everything works, it just creates a new user record that isn't attached to anything. Is there anyway to stop that from happening?
You don't need to tell FactoryBot to create(:user). Just remove it.
factory :post do
user
title { Faker::Lorem.sentence }
end
https://devhints.io/factory_bot

Mongodb ruby driver: edit Collection::View instance filter

When I create Collection::View instance with:
client = Mongo::Client.new('mongodb://127.0.0.1:27017/test')
view = client[:users].find( { name: "Sally" } )
=> #<Mongo::Collection::View:0x69824029475340 namespace='test.users' #filter={"name" => "Sally"} #options={}>
How I can change filter hash of this instance later? This does not work:
view.filter.merge!("age" => 30)
=> #FrozenError: can't modify frozen BSON::Document
I don't think you can. .filter is a method which takes arguments. It is not a hash.
See examples
and also search the code
However you might be able to do something like:
view = lambda { |hash| client[:users].find(hash) }
search_params = { name: "Sally" }
view.(search_params)
view.(search_params.merge!({foo: 'bar'}))

Ruby mongoid aggregation return object

I am doing an mongodb aggregation using mongoid, using ModleName.collection.aggregate(pipeline) . The value returned is an array and not a Mongoid::Criteria, so if a do a first on the array, I get the first element which is of the type BSON::Document instead of ModelName. As a result, I am unable to use it as a model.
Is there a method to return a criteria instead of an array from the aggregation, or convert a bson document to a model instance?
Using mongoid (4.0.0)
I've been struggling with this on my own too. I'm afraid you have to build your "models" on your own. Let's take an example from my code:
class Searcher
# ...
def results(page: 1, per_page: 50)
pipeline = []
pipeline <<
"$match" => {
title: /#{#params['query']}/i
}
}
geoNear = {
"near" => coordinates,
"distanceField" => "distance",
"distanceMultiplier" => 3959,
"num" => 500,
"spherical" => true,
}
pipeline << {
"$geoNear" => geoNear
}
count = aggregate(pipeline).count
pipeline << { "$skip" => ((page.to_i - 1) * per_page) }
pipeline << { "$limit" => per_page }
places_hash = aggregate(pipeline)
places = places_hash.map { |attrs| Offer.new(attrs) { |o| o.new_record = false } }
# ...
places
end
def aggregate(pipeline)
Offer.collection.aggregate(pipeline)
end
end
I've omitted a lot of code from original project, just to present the way what I've been doing.
The most important thing here was the line:
places_hash.map { |attrs| Offer.new(attrs) { |o| o.new_record = false } }
Where both I'm creating an array of Offers, but additionally, manually I'm setting their new_record attribute to false, so they behave like any other documents get by simple Offer.where(...).
It's not beautiful, but it worked for me, and I could take the best of whole Aggregation Framework!
Hope that helps!

Update activerecord relation given a hash of multiple entries

I'm quite new to Rails, so be gentle :)
I have the following models set-up:
class User
has_many :it_certificates, :class_name => 'UserCertificate'
class UserCertificate
belongs_to :skill
Given the following input (in JSON)
{
"certificates":[
{ // update
"id":1,
"name":"Agile Web Dev 2",
"entity":"Agile Masters!",
"non_it":false,
"date_items":{
"month":10,
"year":2012
},
"skill": {
"id":57
}
},
{ // create
"name":"Agile Web Dev 1",
"entity":"Agile Masters!",
"non_it":false,
"date_items":{
"month":10,
"year":2011
},
"skill": {
"id":58
}
}
]
}
How's the easiest way to update the information for the relation it_certificates?
I've been looking to update_all but it doesn't match my needs (it only updates given fields with the same value).
So I've been struggling around with the approach of iterating over each of these records and then update them one-by-one.
I mean struggling because it looks to me there are lots of things I have to care of when the idea of Rails is the opposite.
Thanks in advance!
So, here's my solution for now:
def self.update_from_hash(data, user_id)
self.transaction do
data.each do |certificate|
if certificate[:id] == nil
# create
if !self.create(
:name => certificate[:name],
:entity => certificate[:entity],
:user_id => user_id,
:non_it => certificate[:non_it],
:skill_id => certificate[:skill][:id],
:date => self.build_date_from_items(certificate[:date_items][:month], certificate[:date_items][:year])
)
raise ActiveRecord::Rollback
end
else
# update
if !self.update(certificate[:id], {
:name => certificate[:name],
:entity => certificate[:entity],
:non_it => certificate[:non_it],
:skill_id => certificate[:skill][:id],
:date => self.build_date_from_items(certificate[:date_items][:month], certificate[:date_items][:year])
})
raise ActiveRecord::Rollback
end
end
end
end
return true
end
It works, but I'm still expecting a more elegant solution :)

Mongoid Complex Query Including Embedded Docs

I have a model with several embedded models. I need to query for a record to see if it exists. the issue is that I will have to include reference to multiple embedded documents my query would have to include the following params:
{
"first_name"=>"Steve",
"last_name"=>"Grove",
"email_addresses"=>[
{"type"=>"other", "value"=>"steve#stevegrove.com", "primary"=>"true"}
],
"phone_numbers"=>[
{"type"=>"work_fax", "value"=>"(720) 555-0631"},
{"type"=>"home", "value"=>"(303) 555-1978"}
],
"addresses"=>[
{"type"=>"work", "street_address"=>"6390 N Main Street", "city"=>"Elbert", "state"=>"CO"}
],
}
How can I query for all the embedded docs even though some fields are missing such as _id and associations?
A few things to think about.
Are you sure the query HAS to contain all these parameters? Is there not a subset of this information that uniquely identifies the record? Say (first_name, last_name, and an email_addresses.value). It would be silly to query all the conditions if you could accomplish the same thing in less work.
In Mongoid the where criteria allows you to use straight javascript, so if you know how to write the javascript criteria you could just pass a string of javascript to where.
Else you're left writing a really awkward where criteria statement, thankfully you can use the dot notation.
Something like:
UserProfile.where(first_name: "Steve",
last_name: "Grove",
:email_addresses.matches => {type: "other",
value: "steve#stevegrove.com",
primary: "true"},
..., ...)
in response to the request for embedded js:
query = %{
function () {
var email_match = false;
for(var i = 0; i < this.email_addresses.length && !email_match; i++){
email_match = this.email_addresses[i].value === "steve#stevegrove.com";
}
return this.first_name === "Steve" &&
this.last_name === "Grove" &&
email_match;
}
}
UserProfile.where(query).first
It's not pretty, but it works
With Mongoid 3 you could use elem_match http://mongoid.org/en/origin/docs/selection.html#symbol
UserProfile.where(:email_addresses.elem_match => {value: 'steve#stevegrove.com', primary: true})
This assumes
class UserProfile
include Mongoid::Document
embeds_many :email_addresses
end
Now if you needed to include every one of these fields, I would recommend using the UserProfile.collection.aggregate(query). In this case you could build a giant hash with all the fields.
query = { '$match' => {
'$or' => [
{:email_addresses.elem_match => {value: 'steve#stevegrove.com', primary: true}}
]
} }
it starts to get a little crazy, but hopefully that will give you some insight into what your options might be. https://coderwall.com/p/dtvvha for another example.

Resources