thoughtbot / factory_bot

A library for setting up Ruby objects as test data.
https://thoughtbot.com
MIT License
7.92k stars 2.6k forks source link

Allow accessing Factory.define options. #21

Closed arturaz closed 13 years ago

arturaz commented 15 years ago

Factory.define :building, :class => Building::Mothership do |m| m.x 0 m.x_end { |r| r.x + r.instance_variable_get("@instance").class.property('width') } end

It would be nice to just be able to

Factory.define :building, :class => Building::Mothership do |m| m.x 0 m.x_end { |r, options| r.x + options[:class].property('width') } end

joshuaclayton commented 13 years ago

Can you give a more concrete example of its usage? I guess I'm just not understanding why you couldn't do

Factory.define :building, :class => Building::Mothership do |m|
  m.x 0
  m.x_end { |r, options| r.x + Building::Mothership.property('width') }
end
arturaz commented 13 years ago

Imagine this:

# We define Building::Mothership here just for convenience.
# width is 6
Factory.define :building, :class => Building::Mothership do |m|
  m.x 0
  m.x_end { |r| r.x + r.instance_variable_get("@instance").class.property('width') }
end

# width is 3
Factory.define :building_barracks, :class => Building::Barracks, :parent => :building do |m|
end

# width is 2
Factory.define :building_solar_plant, :class => Building::SolarPlant, :parent => :building do |m|
end

And so on. If I use class name in my factory, i must write appropriate x_end and y_end factory methods, while if I can use @instance.class, I only need to define them once in the base.

Did I explain it clearly?

joshuaclayton commented 13 years ago

You did explain it clearly, and while I understand what you're saying, I feel like there are better ways to go about doing this. For example, if you know x and a building's width, you'd be able to calculate x_end on the superclass instead of setting it in the factory. Does that seem reasonable?

arturaz commented 13 years ago

Yeah, I've done that with building, but sometimes I can't calculate them in superclass.

E.g.

Factory.define :technology, :class => Technology::TestTechnology do |m|
  m.level 0
  m.scientists do |r|
    r.instance_variable_get("@instance").scientists_min(r.level + 1)
  end
  m.association :player
  m.pause_remainder nil
  m.pause_scientists nil
  m.upgrade_ends_at nil
end

When actual code is used, user is supposed to provide value for scientists. In tests I only care that it would specify some condition, therefore I use the @instance hack.

joshuaclayton commented 13 years ago

Although it still feels wrong to me, you can get the @instance variable from the proxy object by calling proxy#result. So, you could do:

Factory.define :user do |user|
  user.age {|u| u.result.class.min_age + 5 }
end

Again, I just want to stress being careful about how much logic is contained in your factories; if the values REALLY matter, that logic shouldn't be replicated in your factories, it should be in your models.

arturaz commented 13 years ago

You should probably add this #result thing to docs so that others would know too ;)

Thanks for your time!

arturaz commented 13 years ago

Actually it's still wrong.

Calling #result tries to save the object, which is not valid in time of construction yet, so it fails with validation errors.

I'll post an example later.