-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Using name-spaced models #199
Comments
There's not a way to do this in Factory Girl specifically, and I don't think it's necessary to add. If you're interested in reducing the noise from the namespacing, I'd think you could create a local variable to reference the full namespace at the top of your file and use that throughout the definitions. user_ns = MyProject::User
FactoryGirl.define do
factory :account, :class => user_ns::Account do
# attributes
end
end |
I think this feature could be interesting, especially when developping in a namespaced engine with Rails which I believe is now a common usage.
@joshuaclayton , Don't you think so? |
I'm still standing behind my comment about assigning a constant to a local variable to reuse; the change wouldn't be insignificant and I don't think it's a common enough problem to have FG address it specifically. |
It's a problem for me because I have a DB with 200+ tables and they are grouped into "modules." Hence, we use namespaces in our code. Not to mention, I'd like to easily leverage factory_girl for PORO's which are often namespaced as they are logically grouped into modules. I suppose I understand that you think it's not common enough to address it specifically, but I imagine this can't be but so hard to patch in. Would you accept a PR? The local variable assignment of a constant is not an elegant "work around" as produces noise in your codebase. It's unconventional to see namespaces referenced as local variables. |
+1 :) |
+1 for clean namespace support |
this would be useful to us also... |
+1 for clean Namespacing implementation |
Based on the number of requests for this feature, if there was a clean and well-tested PR, I would definitely consider getting it merged. |
Since I have come across this issue a couple of times now, I'll try to lead the discussion to a more technical level. I am also very interested in improving factory girl in the context of engines. So I'll split this post into two parts:
I've pushed a first draft implementation to tf/factory_girl:tf-module. I've added an acceptance test for the new behavior. If we a agree on the general direction, I'd continue with some unit level specs for The basic idea is to a add a
In a second step, an optional This enables the following usage:
While this change would make working with namespaced models a bit less verbose, the really interesting thing to me is reusing factories defined in gems/engines. As it stands, this is already possible, but all factories share a global namespace. I think it would be great if factories could be defined in different scopes:
Just like in the Rails router a There could be a
or there could be some sort of import API:
The cleanest way to implement that would probably be to allow for different Still, I think this could really be a worthwhile endeavor, to provide great tooling for engines to supply dependent apps with first class testing facilities. |
I'm bumping into that problem with a "simple" use case: I use I wanted to write some tests, some requiring to manipulate
Namespacing would typically avoid that kind of issue. |
@tf Without having seen the code in your branch, as a new FactoryGirl user I think the :create call is a little ambiguous. I think it would be better if it required some explicit 'namespace' or 'module' option, even if there is only one :user factory defined. (Some context: I work on a project that makes heavy use of namespacing and has several repeated class names, and I'm currently migrating from Machinist 1 to FactoryGirl). A major reason for requiring explicit namespacing on creation would be that adding a new User class in a different namespace would require going back to all the old create (build, etc) calls and explicitly namespace them because the factory lookup is now ambiguous. I could envision a |
@ReneB well, I'd argue that this ambiguity is already there with the current state of things. Right now you'd probably have to define the two factories as:
With the suggested change you could do:
Your
But really there is no reason why this should not be possible. |
@tf I think maybe I should read your code to get things a little clearer, but just to make sure we understand each other: what I was commenting on, was this example: FactoryGirl.define module: SomeEngine do
factory :user do
end
end
FactoryGirl.create(:user).is_a?(SomeEngine::User) I think this factory should not be used when |
I totally agree that the The way I see it, this is one of the cases where inventing a DSL reintroduced problems that are originally solved in the host language. In ruby a class defined as |
@tf well what you're saying here makes complete sense to me and I think that maybe those semantics are really what we want: FactoryGirl.define module: SomeEngine do
factory :user do end # will build a SomeEngine::User
factory :profile do # will build a SomeEngine::Profile
user # gets evaluated to the 'user' factory in the 'some_engine' module
end
end
FactoryGirl.define do
factory :profile do
association :user, module: :some_engine
end
factory :wrong_profile do
user # this won't work
end
end I think, in order to keep the DSL a little DRY, it should be possible to open modules, scopes, namespaces or whatchamacallit in |
Your ideas resonate a lot with my thoughts about scoping. But details aside, all of this would require a major change as to allow the existence of multiple From my point of view, refactoring towards a more decoupled solution would be an interesting improvement for the code base in itself. This would probably have to be a group afford preceded by a much more thorough discussion. Still, I'm hesitant as long as there has been no signal from the maintainers that work on such a change would be supported and is wished for. Let's not forget that our discussion takes place in a closed issue ;) |
+1 for namespace support. I am extracting a gem from an app and I would need that feature... I will use the class: Namespace:Class option until then |
+1 for namespace support. I am writing integration components that map from XCompany::Product to YCompany::Product. Both ends deserve testing. |
To follow up on this thread, there's a couple of examples people have given, One example to define factories: FactoryGirl.define namespace: Whatever do
factory :user
factory :profile
end The questions I currently have are:
Here's how to handle namespacing currently: FactoryGirl.define do
factory :user, class: "Some::Namespace::User" do
profile
end
factory :profile, class: "Some::Namespace::Profile"
end With prefixing so similarly-named objects under different namespaces coexist: FactoryGirl.define do
factory :some_namespace_user, class: "Some::Namespace::User" do
some_namespace_profile
end
factory :some_namespace_profile, class: "Some::Namespace::Profile"
factory :profile
factory :user do
profile
end
end I think introducing another level of abstraction by introducing prefixes is That leads to a solution like this: FactoryGirl.define namespace: "Some::Namespace" do
factory :user do
profile # would always reference the profile defined in this define block - and would raise if the profile factory didn't exist
end
factory :profile
end
FactoryGirl.define do
factory :user do
profile
end
factory :profile
end This reduces the clutter of defining the class name for each factory but makes FactoryGirl.define namespace: "Some::Namespace" do
factory :user do
profile # the profile would need to be defined here
end
factory :profile
end
# This would result in duplicate definitions
FactoryGirl.define do
factory :user do
profile
end
factory :profile
end To be clear, I'm not opposed to more robust namespace support - but I don't |
As described in my post above, the main issue, from my point of view, is that factory names currently live in a single, global scope. It should be possible for two libraries/gems/modules to each define a This would answer your question 2: Associations reference factories from the same scope the enclosing factory is defined in. Same goes for sequences. Regarding question 1: Referencing factories from different scopes via prefixing surely would require dynamic resolution/generation of factory names. To avoid this, one could introduce an explicit import mechanism. # some_gem/spec/factories/users.rb
FactoryGirl.define scope: :some_gem, module: 'SomeGem' do
factory :user do
profile
end
end
# some_gem/spec/factories/profiles.rb
FactoryGirl.define scope: :some_gem, module: 'SomeGem' do
factory :profile do
end
end
# some_app/spec/factories/some_gem.rb
FactoryGirl.define do
import_factory :some_gem, :user, as: :some_gem_user
#or
factory :some_gem_user do
import :some_gem, :user
end
end Now when running the specs of # some_gem/spec/spec_helper.rb
FactoryGirl.default_scope = :some_gem
# some_gem/spec/some_spec.rb
create(:user).is_a?(SomeGem::User)
create(:profile).is_a?(SomeGem::Profile) while in the depending app there is an explicitly defined factory that imports the definition from the gem: # some_app/spec/some_spec.rb
create(:some_gem_user).is_a?(SomeGem::User) The scope/default scope concept would also be completely backward compatible: Supplying no Does this make sense? |
FYI, given this class: module ModuleA
class Foo
attr_accessor :uuid
end
end Then this works for me: module ModuleA
FactoryGirl.define do
factory :module_a_foo, class: Foo do
sequence(:uuid) { UUIDTools::UUID.random_create }
end
end
end So, in theory, you could do something like this: module ModuleA
FactoryGirl.define(factory_name_prefix: :module_a) do
factory :foo, class: Foo do
sequence(:uuid) { UUIDTools::UUID.random_create }
end
end
end
FactoryGirl.build(:module_a_foo) Just a random thought. |
Yes, but this does not solve the problem of having to repeat the, for example, gem name all over the gem codebase. Always having to write |
@joshuaclayton I believe that having this in FG would benefit not only engine creators, since namespaced models are not uncommon in non-trivial apps. @tf Thank you for leading this discussion.
Implementing this would be trivial. |
I wish I had this now, as I am struggling with a large project with many name-spaces. |
I still have a number of concerns around edge cases, implied global state around RSpec blocks, and overhead (see #199 (comment) for history). I'd love to see people implement this without modifying FG and see what they run into. A few different implementations might all help us understand what issues might arise. As an example of namespace support with zero modification of FG, please take a look at 652818b. This demonstrates default namespaces, namespaces wrapping multiple factories, and automatic name prefixing in instances where the same class name exists under multiple namespaces. Note that this doesn't handle association prefixes, which I demonstrated in the |
Any progress with it?
I spend two days already to make it work without any success. |
@evgen3d without access to the codebase, I'm afraid I can't offer much help. Seems this is related to load order (especially if you expect that constant to be available), but I don't have specific guidance here, apart from ensuring the engine is loaded before the factories file is. |
See: thoughtbot#199 I have a number of clients who use FactoryBot and are working through the difficult challene of decomposing a large monolith into either domain-driven namespaces; or small gems/engines that are "plugged into" a larger monolith. In most cases, we've been able to get away with keeping the factories bundled with their extracted modules and using distinct names. However there's also been a bit of copy-pasting of factory code back into the monolith in cases where the names really _do_ need to be shared. This is an attempt to bring @joshuaclayton's example of namespacing into the core FactoryBot DSL. If you'd prefer I packaged this as it's own gem, I'd be happy to do so; but I'd be stoked to see this in the core FactoryBot package!
Hello folks,
I have Rails projects with name-spaced models. At the moment my factories look like the following with a lot of clutter, because of the name-spaced models:
They are working, though it doesn't feel very DRY. Is (or will be) there any way to define the name-space once? Like the following?
The text was updated successfully, but these errors were encountered: