Fabricate.build with validations

217 views
Skip to first unread message

jev...@panoramaed.com

unread,
Jul 26, 2013, 2:27:11 PM7/26/13
to fabrica...@googlegroups.com
I've run into a Fabrication issue and I'm not sure how best to go about solving it. I have three classes: Foo, Bar, and Linker. Foos have many Bars through Linker, and Bars have many Foos through Linker. Foos can exist without any Bars though, and vice versa. To make sure that nothing goes wonky with the associations, I have a validation on Linker that checks for the presence of a Foo and Bar (Linkers are only used to join a Foo and a Bar).

My problem is that I want to write a Fabricator that creates a Foo with associated Bars. Because of the order of validations, if I do:

Fabricator(:foo_with_bars, class_name: Foo) do
  bars(count: 3) { Fabricate(:bar) }
end

This doesn't work, because Bars are created first, then Linkers, then Foo. Since at the time Linkers are validated, Foo hasn't been saved, the validation fails.

I can instead do this with a callback like so:

Fabricator(:foo_with_bars, class_name: Foo) do
  after_create do |foo|
    foo.bars = (1..3).map { Fabricate(:bar) }
  end
end

This works with Fabricate, but if I use Fabricate.build the callback never gets called since we never use create.

My general question is: is there a pattern for this sort of thing in Fabrication, where an after_create callback is used if we're using Fabricate(), but a regular association setup is used if we're doing Fabricate.build()? I can always create helper methods that do this for me, but it would be nice if Fabrication had an elegant solution.

Paul Elliott

unread,
Jul 26, 2013, 2:53:34 PM7/26/13
to fabrica...@googlegroups.com
When you call build on any model it will cascade downward to any declared associations as well, so the validations should not be invoked during the fabrication lifecycle at all. I am surprised it isn't working as you should just be able to do this, which is roughly the same as what you provided.

Fabricator(:foo_with_bars, class_name: Foo) do
  bars(count: 3)
end

Which version of Fabrication are you using? Can you post the relevant model code?

-- Paul

--
You received this message because you are subscribed to the Google Groups "fabrication" group.
To unsubscribe from this group and stop receiving emails from it, send an email to fabricationge...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

jev...@panoramaed.com

unread,
Jul 26, 2013, 2:59:34 PM7/26/13
to fabrica...@googlegroups.com
Thanks for the speedy reply! I don't think I was clear with my initial post; running the code you've shown does work, but only for building, as when I use Fabricate() the validations fail as I described.

The problem is that my first snippet works only when building and my second works only when creating, and ideally I'd have one fabricator that works in both cases.

Paul Elliott

unread,
Jul 26, 2013, 3:01:50 PM7/26/13
to fabrica...@googlegroups.com
Oh, you're right. I did not understand the issue. You should be able to do this then:

Fabricator(:foo_with_bars, from: Foo) do
  bars(count: 3) { Fabricate.build(:bar) }
end

That way you can still easily override it if you'd like. ActiveRecord will cascade the save across the whole object graph when Fabrication tries to save the foo you are generating.

-- Paul

Jacob Evelyn

unread,
Jul 26, 2013, 4:51:17 PM7/26/13
to fabrica...@googlegroups.com
Ah, thanks for the tip. When I switch to this code, however, Fabricate fails at the validation step because Linker has no IDs for the bars that are built.



--
You received this message because you are subscribed to a topic in the Google Groups "fabrication" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/fabricationgem/BkYwmo0rbME/unsubscribe.
To unsubscribe from this group and all its topics, send an email to fabricationge...@googlegroups.com.

Jacob Evelyn

unread,
Jul 26, 2013, 5:01:32 PM7/26/13
to fabricationgem
Alternatively, if there was a way within a fabricator to detect the build strategy (build vs. regular fabricate) and perform conditional logic, that would also work.

Paul Elliott

unread,
Jul 26, 2013, 6:04:47 PM7/26/13
to fabrica...@googlegroups.com
You could know if it were a build, although I wouldn't recommend it. That isn't part of the public API and your code will likely break in the future.

Can I see the code for the Linker model?

-- Paul

jev...@panoramaed.com

unread,
Jul 29, 2013, 6:25:41 PM7/29/13
to fabrica...@googlegroups.com
It's pretty much just:

class Linker
  belongs_to :foo
  belongs_to :bar
  validates :foo, :bar, presence: true
end

I know it seems silly to have but we need it for another unrelated aspect of our design.

Paul Elliott

unread,
Jul 29, 2013, 6:45:57 PM7/29/13
to fabrica...@googlegroups.com
Are you sure you are validating :foo and not :foo_id? I have seen issues when that is the case.

-- Paul

Jacob Evelyn

unread,
Jul 29, 2013, 6:48:20 PM7/29/13
to fabricationgem
Yep, definitely validating :foo. Overall, this isn't a huge problem. I can Just have one fabricator for building and one for fabricating (creating), and indicate which is which in their names.

Paul Elliott

unread,
Jul 30, 2013, 11:22:29 AM7/30/13
to fabrica...@googlegroups.com
I've done some research this morning and found that it is partially an issue with ActiveRecord.


It says you should be able to get it to work by adding inverse_of to the associations in question. I spent the better part of the morning trying to get this to work but to no avail.

What I do know is the the issue also has to do with how fabrication sets fields. With AR, it passes them in to `build` and lets AR's mass assignment handle things. That means that it comes in looking like this:

`Foo.build(bars: [bar1, bar2, etc])`

Apparently the preferred way to do it is to build the parent and call build on the association proxy to create child records. Doing this within fabrication would be extremely difficult without a huge refactor of the library though.

I think there may be a way to get it to work by creating the join table from the fabricator instead of the real target, but I think that would be a worse solution than just having two separate fabricators.

Hope this helps.

-- Paul

Jacob Evelyn

unread,
Jul 30, 2013, 2:54:36 PM7/30/13
to fabricationgem
Wow, thanks so much for the thorough researching. I really appreciate all the help.
Reply all
Reply to author
Forward
0 new messages