Add possibility to include helper methods into FactoryGirl to be able to use them on factory definition #564

Closed
pkuczynski opened this Issue Sep 18, 2013 · 26 comments

Comments

Projects
None yet
@pkuczynski

I have a spec/my_helper.rb in a form of:

module MyHelper
  def my_method
    ...
  end
end

I would like to use this method both in RSpec and during factory definition:

FactoryGirl.define do
  factory :my_factory do
    ...
    my_value: { my_method }
    ...
  end
end

Unfortunately this is not possible or I don't know how to do that? RSpec provides very nice way of including helpers in spec_helper.rb:

require 'my_helper'

RSpec.configure do |config|
   ...
   config.include MyHelper
   ...
end

Would it be possible to add something similar to FactoryGirl?

@joshuaclayton

This comment has been minimized.

Show comment
Hide comment
@joshuaclayton

joshuaclayton Sep 18, 2013

Member

@pkuczynski this has come up a couple times in the past eight months or so, and I'm definitely interested in pursuing this a bit more. I'd love to come up with some ideas about syntax and usage before implementation, but I think the following syntax would be good:

FactoryGirl.define do
  # this could be something like a helper for converting fixture files
  # like images into actual files for Paperclip, since multiple models
  # could take advantage of the method
  include GlobalLevelExtensionsToFactoryGirl

  factory :user do
    include FactorySpecificExtensions

    trait :more_extensions do
      include EvenMoreExtensions
    end
  end
end

In this example, all factories would have the ability to call methods from GlobalLevelExtensionsToFactoryGirl. I'm not sure how often we'd be able to recommend this because it would likely clobber any existing methods on objects (more on this later). The user factory would have methods from the factory-specific extensions and the global extensions, and if it had the more_extensions trait added, the methods there would also be available.

I'm interested in order of method precedence in this example. If any of the modules collided with methods on the the object the factory is building, which takes precedence? I think it's safe to say that the modules at the highest granularity would take precedence, just like Ruby's inheritance (if FactorySpecificExtensions and EvenMoreExtensions both defined the same method, EvenMoreExtensions would be the method chosen).

Let me know your thoughts on the syntax and the desired behavior, I'll run it past some other devs, and we'll see if we can come to a consensus! Thanks!

Member

joshuaclayton commented Sep 18, 2013

@pkuczynski this has come up a couple times in the past eight months or so, and I'm definitely interested in pursuing this a bit more. I'd love to come up with some ideas about syntax and usage before implementation, but I think the following syntax would be good:

FactoryGirl.define do
  # this could be something like a helper for converting fixture files
  # like images into actual files for Paperclip, since multiple models
  # could take advantage of the method
  include GlobalLevelExtensionsToFactoryGirl

  factory :user do
    include FactorySpecificExtensions

    trait :more_extensions do
      include EvenMoreExtensions
    end
  end
end

In this example, all factories would have the ability to call methods from GlobalLevelExtensionsToFactoryGirl. I'm not sure how often we'd be able to recommend this because it would likely clobber any existing methods on objects (more on this later). The user factory would have methods from the factory-specific extensions and the global extensions, and if it had the more_extensions trait added, the methods there would also be available.

I'm interested in order of method precedence in this example. If any of the modules collided with methods on the the object the factory is building, which takes precedence? I think it's safe to say that the modules at the highest granularity would take precedence, just like Ruby's inheritance (if FactorySpecificExtensions and EvenMoreExtensions both defined the same method, EvenMoreExtensions would be the method chosen).

Let me know your thoughts on the syntax and the desired behavior, I'll run it past some other devs, and we'll see if we can come to a consensus! Thanks!

@pkuczynski

This comment has been minimized.

Show comment
Hide comment
@pkuczynski

pkuczynski Sep 18, 2013

Thanks @joshuaclayton for quick answer! this indeed shown up on couple of questions on StackOverflow, but nobody could provide any working and sensible solution. The way you propose it is perfectly fine for me including the method precedence. It was actually my first attempt to solve this problem, but obviously it didn't work. That could be a hint that your proposal is natural behaviour to Ruby/Rails developer.

However the way RSpec have done this by global inclusion works well for me too, as it allows to include global helpers usable across whole FactoryGirl. So the alternative proposal from my side would be:

require 'my_helper'

FactoryGirl.configure do |config|
   ...
   config.include MyHelper
   ...
end

Additionally it would be even better if I could somehow include the same set of Helpers in RSpec and FactoryGirl in one step, although I have no particular idea of this kind of notation in my mind.

I know I would be able to achieve the same results for global helpers inclusion using include GlobalLevelExtensionsToFactoryGirl, although I am not sure if that would work in my setup, where I have multiple files in spec/factories folder and each of them starts from FactoryGirl.define do?

So far I achieved what I need by referencing to the methods via helper module name:

FactoryGirl.define do
  factory :my_factory do
    ...
    my_value: { MyHelper.my_method }
    ...
  end
end

That is pretty annoying as I have to explicitly use module name, but whats worse is that if I want to use the same method in RSpec specs without the module name, I had to build my helper in this way:

module MyHelper
  def my_method
    ...
  end

  def self.my_method
    my_method
  end
end

Highly annoying and confusing method duplication, but I keep it for now as a workaround to the problem, hoping you can help me solve it soon :)

Thanks @joshuaclayton for quick answer! this indeed shown up on couple of questions on StackOverflow, but nobody could provide any working and sensible solution. The way you propose it is perfectly fine for me including the method precedence. It was actually my first attempt to solve this problem, but obviously it didn't work. That could be a hint that your proposal is natural behaviour to Ruby/Rails developer.

However the way RSpec have done this by global inclusion works well for me too, as it allows to include global helpers usable across whole FactoryGirl. So the alternative proposal from my side would be:

require 'my_helper'

FactoryGirl.configure do |config|
   ...
   config.include MyHelper
   ...
end

Additionally it would be even better if I could somehow include the same set of Helpers in RSpec and FactoryGirl in one step, although I have no particular idea of this kind of notation in my mind.

I know I would be able to achieve the same results for global helpers inclusion using include GlobalLevelExtensionsToFactoryGirl, although I am not sure if that would work in my setup, where I have multiple files in spec/factories folder and each of them starts from FactoryGirl.define do?

So far I achieved what I need by referencing to the methods via helper module name:

FactoryGirl.define do
  factory :my_factory do
    ...
    my_value: { MyHelper.my_method }
    ...
  end
end

That is pretty annoying as I have to explicitly use module name, but whats worse is that if I want to use the same method in RSpec specs without the module name, I had to build my helper in this way:

module MyHelper
  def my_method
    ...
  end

  def self.my_method
    my_method
  end
end

Highly annoying and confusing method duplication, but I keep it for now as a workaround to the problem, hoping you can help me solve it soon :)

@joshuaclayton

This comment has been minimized.

Show comment
Hide comment
@joshuaclayton

joshuaclayton Sep 20, 2013

Member

@pkuczynski In your module, you can throw an extend self at the top of the file, which will allow you to call MyHelper.my_method from within FactoryGirl for now. I've been trying to come up with a way to make FG more Rubyish and this is definitely in the list of things I want to do, and it goes along with some other ideal functionality/refactoring-of-guts. It turns out this is a tricky problem, but I'll keep you posted once I get somewhere with that and am able to tackle this with it.

Thanks!

Member

joshuaclayton commented Sep 20, 2013

@pkuczynski In your module, you can throw an extend self at the top of the file, which will allow you to call MyHelper.my_method from within FactoryGirl for now. I've been trying to come up with a way to make FG more Rubyish and this is definitely in the list of things I want to do, and it goes along with some other ideal functionality/refactoring-of-guts. It turns out this is a tricky problem, but I'll keep you posted once I get somewhere with that and am able to tackle this with it.

Thanks!

@pkuczynski

This comment has been minimized.

Show comment
Hide comment
@pkuczynski

pkuczynski Sep 25, 2013

extend self works great! Thanks for the tip :)

Looking forward to see some new stuff in FactoryGirl. Good luck! :)

extend self works great! Thanks for the tip :)

Looking forward to see some new stuff in FactoryGirl. Good luck! :)

@pkuczynski

This comment has been minimized.

Show comment
Hide comment
@pkuczynski

pkuczynski Nov 7, 2013

@joshuaclayton, does this mean its completed or you abandoned this feature?

@joshuaclayton, does this mean its completed or you abandoned this feature?

@joshuaclayton

This comment has been minimized.

Show comment
Hide comment
@joshuaclayton

joshuaclayton Nov 7, 2013

Member

@pkuczynski I'm closing this issue but not abandoning that feature - just don't want to have this one open, since that one is very much a work in progress and will be happening independently of this issue specifically.

Member

joshuaclayton commented Nov 7, 2013

@pkuczynski I'm closing this issue but not abandoning that feature - just don't want to have this one open, since that one is very much a work in progress and will be happening independently of this issue specifically.

@jaredbeck

This comment has been minimized.

Show comment
Hide comment
@jaredbeck

jaredbeck Feb 7, 2014

Contributor

I have a question which I think is similar: how to specify an attribute common to all factories.

http://stackoverflow.com/questions/21639460/how-to-specify-an-attribute-common-to-all-factories

I can't be the first person to run into this, so I must be missing something basic... :)

Contributor

jaredbeck commented Feb 7, 2014

I have a question which I think is similar: how to specify an attribute common to all factories.

http://stackoverflow.com/questions/21639460/how-to-specify-an-attribute-common-to-all-factories

I can't be the first person to run into this, so I must be missing something basic... :)

@joshuaclayton

This comment has been minimized.

Show comment
Hide comment
@joshuaclayton

joshuaclayton Feb 8, 2014

Member

@jaredbeck from that example, you could use traits:

FactoryGirl.define do
  trait :timestamps do
    created_at { Time.current }
    updated_at { Time.current }
  end

  factory :foo do
    timestamps
  end
end
Member

joshuaclayton commented Feb 8, 2014

@jaredbeck from that example, you could use traits:

FactoryGirl.define do
  trait :timestamps do
    created_at { Time.current }
    updated_at { Time.current }
  end

  factory :foo do
    timestamps
  end
end
@jaredbeck

This comment has been minimized.

Show comment
Hide comment
@jaredbeck

jaredbeck Feb 8, 2014

Contributor

you could use traits

Thanks for the example! I thought about that, but then I'd have to update every call site in my entire test suite to use said trait, right?

Contributor

jaredbeck commented Feb 8, 2014

you could use traits

Thanks for the example! I thought about that, but then I'd have to update every call site in my entire test suite to use said trait, right?

@joshuaclayton

This comment has been minimized.

Show comment
Hide comment
@joshuaclayton

joshuaclayton Feb 9, 2014

Member

@jaredbeck yes, just like the example where you'd have to instance_eval to include the proc, you'd have to do the same by using a trait.

Seems like a bit of an anti-pattern to have to specify attributes for every model; can you provide some context here?

Member

joshuaclayton commented Feb 9, 2014

@jaredbeck yes, just like the example where you'd have to instance_eval to include the proc, you'd have to do the same by using a trait.

Seems like a bit of an anti-pattern to have to specify attributes for every model; can you provide some context here?

@jaredbeck

This comment has been minimized.

Show comment
Hide comment
@jaredbeck

jaredbeck Feb 10, 2014

Contributor

Seems like a bit of an anti-pattern to have to specify attributes for every model;

I agree, it felt odd to me too.

can you provide some context here?

I thought I had to provide values for the created_at and updated_at timestamps which have NOT NULL constraints as of rails 4.0, but I was mistaken. Rails handles the assignment of the timestamps and it's not necessary to include them in factories.

Thanks for your help!

Contributor

jaredbeck commented Feb 10, 2014

Seems like a bit of an anti-pattern to have to specify attributes for every model;

I agree, it felt odd to me too.

can you provide some context here?

I thought I had to provide values for the created_at and updated_at timestamps which have NOT NULL constraints as of rails 4.0, but I was mistaken. Rails handles the assignment of the timestamps and it's not necessary to include them in factories.

Thanks for your help!

@joshuaclayton

This comment has been minimized.

Show comment
Hide comment
@joshuaclayton

joshuaclayton Feb 12, 2014

Member

@jaredbeck yep, Rails will do it automatically (and has since Rails 2!) - glad to hear this is all sorted out!

Member

joshuaclayton commented Feb 12, 2014

@jaredbeck yep, Rails will do it automatically (and has since Rails 2!) - glad to hear this is all sorted out!

@Crystark

This comment has been minimized.

Show comment
Hide comment
@Crystark

Crystark Mar 21, 2014

It seems i'm able to do in config\initializers\factory_girl.rb:

if defined? FactoryGirl
  FactoryGirl::SyntaxRunner.send(:include, My::Tests::GlobalHelpers)
end

My::Tests::GlobalHelpers defines a method called my_helper so i'm now able to do:

FactoryGirl.define do
  factory :my_factory do
    my_value                { my_helper }
  end
end

It seems i'm able to do in config\initializers\factory_girl.rb:

if defined? FactoryGirl
  FactoryGirl::SyntaxRunner.send(:include, My::Tests::GlobalHelpers)
end

My::Tests::GlobalHelpers defines a method called my_helper so i'm now able to do:

FactoryGirl.define do
  factory :my_factory do
    my_value                { my_helper }
  end
end
@richhollis

This comment has been minimized.

Show comment
Hide comment
@richhollis

richhollis Apr 15, 2014

@Crystark that worked great for me - I wanted to be able to use some helper functionality in a rspec spec helper, so I used the same logic for the spec_helper file require. Thanks for sharing.

if defined? FactoryGirl
  Dir[Rails.root.join("spec/support/spec_helpers/*.rb")].each {|f| require f}
  FactoryGirl::SyntaxRunner.send(:include, ReservationHelpers)
end

@Crystark that worked great for me - I wanted to be able to use some helper functionality in a rspec spec helper, so I used the same logic for the spec_helper file require. Thanks for sharing.

if defined? FactoryGirl
  Dir[Rails.root.join("spec/support/spec_helpers/*.rb")].each {|f| require f}
  FactoryGirl::SyntaxRunner.send(:include, ReservationHelpers)
end
@blueplanet

This comment has been minimized.

Show comment
Hide comment
@fuzzyalej

This comment has been minimized.

Show comment
Hide comment
@fuzzyalej

fuzzyalej Jan 30, 2015

What's the status of this?

What's the status of this?

@git-toni

This comment has been minimized.

Show comment
Hide comment
@git-toni

git-toni Aug 14, 2015

+1! Although the proposed approaches do work :)

+1! Although the proposed approaches do work :)

@arnlen

This comment has been minimized.

Show comment
Hide comment
@arnlen

arnlen Oct 23, 2015

Up, since it's the first Google result on the topic.

Is the proposed solution still the only one available?
Any plan to include a more "native" way to be able to require helper methods inside factories?

arnlen commented Oct 23, 2015

Up, since it's the first Google result on the topic.

Is the proposed solution still the only one available?
Any plan to include a more "native" way to be able to require helper methods inside factories?

@arthwood

This comment has been minimized.

Show comment
Hide comment
@arthwood

arthwood Nov 18, 2015

What I did is I extended FactoryGirl::Syntax::Methods module that I include in RSpec anyway:

In support/factory_girl.rb:

require_relative 'helpers/factory_girl'

RSpec.configure do |config|
  config.include FactoryGirl::Syntax::Methods
  # ...
end

In support/helpers/factory_girl.rb:

module FactoryGirl
  module Syntax
    module Methods
      def my_helper_method
        # ...
      end
    end
  end
end

What I did is I extended FactoryGirl::Syntax::Methods module that I include in RSpec anyway:

In support/factory_girl.rb:

require_relative 'helpers/factory_girl'

RSpec.configure do |config|
  config.include FactoryGirl::Syntax::Methods
  # ...
end

In support/helpers/factory_girl.rb:

module FactoryGirl
  module Syntax
    module Methods
      def my_helper_method
        # ...
      end
    end
  end
end
@finiteautomata

This comment has been minimized.

Show comment
Hide comment
@finiteautomata

finiteautomata Feb 17, 2016

Similar to what the OP asked for, I wanted to create a helper function that automatically creates traits for each possible value of an enum of a class.

The module I wrote is

module FactoryGirl
  module DeclarationHelpers
    #
    # This helper converts an enum to traits that represent each possible value
    # Suppose the following case:
    #
    # Example:
    #
    #   class Foo
    #     enum status: [:a, :b, :c]
    #   end
    #
    #   factory :foo do
    #     traits_for :status
    #   end
    #
    #  This is the same as doing
    #
    #   factory :foo do
    #     trait :a do
    #       status "a"
    #     end
    #
    #     trait :b do
    #       status "b"
    #     end
    #
    #     trait :c do
    #       status "c"
    #     end
    #   end
    #
    # obs: klass was the only form I found to infer the proper class

    def traits_for(klass, *enum_names)
      enum_names.each do |enum_name|
        pluralized_name = enum_name.to_s.pluralize

        # Ok, it says 'keys' but they are the possible values of the enum
        values = klass.send(pluralized_name).keys

        values.each do |value|
          trait value.to_sym do
            send(enum_name, value)
          end
        end
      end
    end
  end
end

I figure that is different from what the OP and following posters wanted to do. I tried to do the include in FactoryGirl::SyntaxRunner with no success, as well as many other hacky things (trying to include it into FactoryGirl::Declaration and other nasty stuff).

In spite of not being totaly DRY and nice, I came up with this solution

require_relative '../support/declaration_helpers'

FactoryGirl.define do
  factory :mymodel do
    extend FactoryGirl::DeclarationHelpers

    # Some attributes here

    traits_for Animal, :status, :classification, :kind
  end
end

Is there any other possibility instead of extending it in every factory?

Similar to what the OP asked for, I wanted to create a helper function that automatically creates traits for each possible value of an enum of a class.

The module I wrote is

module FactoryGirl
  module DeclarationHelpers
    #
    # This helper converts an enum to traits that represent each possible value
    # Suppose the following case:
    #
    # Example:
    #
    #   class Foo
    #     enum status: [:a, :b, :c]
    #   end
    #
    #   factory :foo do
    #     traits_for :status
    #   end
    #
    #  This is the same as doing
    #
    #   factory :foo do
    #     trait :a do
    #       status "a"
    #     end
    #
    #     trait :b do
    #       status "b"
    #     end
    #
    #     trait :c do
    #       status "c"
    #     end
    #   end
    #
    # obs: klass was the only form I found to infer the proper class

    def traits_for(klass, *enum_names)
      enum_names.each do |enum_name|
        pluralized_name = enum_name.to_s.pluralize

        # Ok, it says 'keys' but they are the possible values of the enum
        values = klass.send(pluralized_name).keys

        values.each do |value|
          trait value.to_sym do
            send(enum_name, value)
          end
        end
      end
    end
  end
end

I figure that is different from what the OP and following posters wanted to do. I tried to do the include in FactoryGirl::SyntaxRunner with no success, as well as many other hacky things (trying to include it into FactoryGirl::Declaration and other nasty stuff).

In spite of not being totaly DRY and nice, I came up with this solution

require_relative '../support/declaration_helpers'

FactoryGirl.define do
  factory :mymodel do
    extend FactoryGirl::DeclarationHelpers

    # Some attributes here

    traits_for Animal, :status, :classification, :kind
  end
end

Is there any other possibility instead of extending it in every factory?

@oniksfly

This comment has been minimized.

Show comment
Hide comment
@oniksfly

oniksfly Jun 16, 2016

@finiteautomata thank you for your solution. BTW you have ability to declare after/before blocks in such helper methods.

@finiteautomata thank you for your solution. BTW you have ability to declare after/before blocks in such helper methods.

@jessieay jessieay referenced this issue in 18F/micropurchase Jul 20, 2016

Merged

Miles of style #908

@itay-grudev

This comment has been minimized.

Show comment
Hide comment
@itay-grudev

itay-grudev Jun 14, 2017

How about simply:

# spec/factories.rb
include SomeHelper

FactoryGirl.define do
  # ...
end

This allowed me to reuse Rails Helpers in factories.

How about simply:

# spec/factories.rb
include SomeHelper

FactoryGirl.define do
  # ...
end

This allowed me to reuse Rails Helpers in factories.

@arthwood

This comment has been minimized.

Show comment
Hide comment
@arthwood

arthwood Jun 15, 2017

@itay-grudev but that would allow you to use helper methods only in the file where it is included, whereas extending FactoryGirl::Syntax::Methods gives you access to helper methods in all factories (and test examples) without explicit inclusion.

@itay-grudev but that would allow you to use helper methods only in the file where it is included, whereas extending FactoryGirl::Syntax::Methods gives you access to helper methods in all factories (and test examples) without explicit inclusion.

@itay-grudev

This comment has been minimized.

Show comment
Hide comment
@itay-grudev

itay-grudev Jun 15, 2017

@arthwood That's true, but it would also pollute the namespace which usually is not a good thing. Also tests tend to be specific so you would only need to include the relevant to that test helpers.

P.S. Nice telescope. I was working on 50/70cm one two nights ago.

itay-grudev commented Jun 15, 2017

@arthwood That's true, but it would also pollute the namespace which usually is not a good thing. Also tests tend to be specific so you would only need to include the relevant to that test helpers.

P.S. Nice telescope. I was working on 50/70cm one two nights ago.

@nomasprime

This comment has been minimized.

Show comment
Hide comment
@nomasprime

nomasprime May 16, 2018

Has there been any progress, @joshuaclayton?

Would like to be able to use file_fixture.

nomasprime commented May 16, 2018

Has there been any progress, @joshuaclayton?

Would like to be able to use file_fixture.

@ruiccarvalho

This comment has been minimized.

Show comment
Hide comment
@ruiccarvalho

ruiccarvalho May 29, 2018

I'm interested in this too. Trying to use file_fixture and upload_fixture_file as these methods seem to make more sense to use inside factories than on the testing examples themselves, but right now it just looks like it's too much trouble to make it work that way.

I'm interested in this too. Trying to use file_fixture and upload_fixture_file as these methods seem to make more sense to use inside factories than on the testing examples themselves, but right now it just looks like it's too much trouble to make it work that way.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment