Closed jfelchner closed 8 years ago
I had this same issue, and I solved it using transient attributes. It's not elegant but not bad either. I do agree it's probably going to become a more common scenario. We make liberal use of hstore
attributes on Postgres.
Here is your example adapted, in case it helps someone else:
FactoryGirl.define do
factory :user do
transient do
configuration_snoozed false
configuration_all_types_enabled false
end
trait :snoozed do
configuration_snoozed true
end
trait :all_types_enabled
configuration_all_types_enabled true
end
# Build up an hstore attribute (or whatever Hash-based thing you have)
# named `configuration` based on value of transient attributes
configuration do
hash = {}
hash.deep_merge!(
'notifications' => {
'snooze' => {
'enabled' => true,
'starting_at' => Time.now,
'ending_at' => Time.now,
}
}) if configuration_snoozed
hash.deep_merge!(
'notifications' => {
'text' => {
'enabled' => true,
},
'email' => {
'enabled' => true,
},
}) if configuration_all_types_enabled
hash
end
end
end
Then:
create(:user, :snoozed, :all_types_enabled) # should do what you want
If you don't want the syntactic sugar of traits (or want to avoid adding an extra layer of redirection), just drop the traits and create like so:
create(:user, configuration_snoozed: true, configuration_all_types_enabled: true) # same
@ArthurN transient attributes seems like an excellent approach here!
Hi all, very nice I could find this thread to confirm that for now, there's no feature to merge Hash
attributes in _factorybot.
@ArthurN's solution is quite nice, yet it implies adding one transient attribute per trait. This is a bit redundant, so I played around and found another solution, for anyone interested (probably has downsides, too!)
EDIT: Found an even better solution, basically using after(:build) do |object|
+ a merge
method:
FactoryBot.define do
class Meal
attr_accessor :data
def merge_data(more_data)
hash = data || {}
self.data = hash.deep_merge(more_data)
end
end
factory :meal do
trait :ab do
after(:build) do |object|
object.merge_data(
{
a: { label: "Apple" },
b: { label: "Banana" },
}
)
end
end
trait :bc do
after(:build) do |object|
object.merge_data(
{
b: { label: "Baguette" },
c: { label: "Cheese " },
}
)
end
end
end
end
Then we're able to mix traits and the Hash
structures will be deep merged in the final object created:
object = create(:meal, :ab, :bc)
object.data.as_json
=> {"a"=>{"label"=>"Apple"}, "b"=>{"label"=>"Baguette"}, "c"=>{"label"=>"Cheese "}}
object = create(:meal, :bc, :ab)
object.data.as_json
=> {"b"=>{"label"=>"Banana"}, "c"=>{"label"=>"Cheese "}, "a"=>{"label"=>"Apple"}}
Notice how the latest trait's nested keys override the same keys that were present in previous traits.
As a conclusion, I'd love to have that natively available in _factorybot!
In the docs it says that the latest trait's attribute definition takes precedence over any prior traits and this works great for regular attributes, but for something like a configuration hash, the desire would be to merge the results of multiple traits together.
I tried doing this using
@instance
(I know, I know) and that worked great for one trait, but predictably based on the docs, only the last one executed rather than being stacked.Here's a concrete example:
I would prefer that the resulting object contained a configuration hash of:
So my question is, is this currently possible and if not, as the prevalence of JSON columns in the DB increases, is this something that might be coming down the line?