Skip to content

Commit

Permalink
Add jennifer association support
Browse files Browse the repository at this point in the history
  • Loading branch information
imdrasil committed Aug 24, 2017
1 parent 04159de commit 39256d6
Show file tree
Hide file tree
Showing 11 changed files with 247 additions and 21 deletions.
16 changes: 16 additions & 0 deletions README.md
Expand Up @@ -190,6 +190,22 @@ It provides direct creating methods same as for building:
FilmFactory.create([:bad], {:name => "Atilla"})
```

Also any association could be described on the factory or trait level:

```crystal
class FilmFactory < Factory::Jennifer::Base
association :author
association :actor, UserFactory, options: {name: "Artemius Fault"}
end
```

Allowed arguments:

- `:name` - first argument - represent model association name (mandatory)
- `:factory` - represents factory class (optional); is defaulted from association name
- `:strategy` - represents creation strategy; optional; default is "create" (also "build" is allowed)
- `:options` - represents extra arguments to association factory; optional

## Development

For development postgres is required because of testing integration with Jennifer.
Expand Down
53 changes: 53 additions & 0 deletions spec/factory/jennifer/base_spec.cr
Expand Up @@ -11,6 +11,52 @@ describe Factory::Jennifer::Base do
::Jennifer::Adapter.adapter.rollback_transaction
end

describe "%association" do
it "uses factory's defined association" do
film = Factory.create_custom_film([:bad, :hit])
expect(film.author.nil?).wont_equal(true)
expect(film.author!.name).must_match(/Author \d*$/)
end

it "uses trait's author if it is given" do
film = Factory.create_fiction_film([:with_special_author])
expect(film.author.nil?).wont_equal(true)
expect(film.author!.name).must_equal("Special Author")
end

it "uses given overrides for factory" do
film = Factory.create_fiction_film([:with_special_author])
expect(film.author!.name).must_equal("Special Author")
end

it "uses parent association if current factory has no" do
film = Factory.create_fiction_film
expect(film.author.nil?).must_equal(false)
end

it "creates object without association if there is no one" do
film = Factory.create_film
expect(film.author.nil?).must_equal(true)
end
end

describe "%factory_creators" do
it "defines all create methods on module level" do
expect(Factory.create_custom_film.new_record?).must_equal(false)
expect(Factory.create_custom_film(name: "New film").new_record?).must_equal(false)
expect(Factory.create_custom_film({:name => "New"}).new_record?).must_equal(false)
expect(Factory.create_custom_film([:bad]).new_record?).must_equal(false)
expect(Factory.create_custom_film([:bad], name: "new").new_record?).must_equal(false)
expect(Factory.create_custom_film([:bad], {:name => "new"}).new_record?).must_equal(false)
expect(Factory.create_custom_film(1)[0].new_record?).must_equal(false)
expect(Factory.create_custom_film(1, name: "asd")[0].new_record?).must_equal(false)
expect(Factory.create_custom_film(1, [:bad])[0].new_record?).must_equal(false)
expect(Factory.create_custom_film(1, {:name => "asd"})[0].new_record?).must_equal(false)
expect(Factory.create_custom_film(1, [:bad], name: "asd")[0].new_record?).must_equal(false)
expect(Factory.create_custom_film(1, [:bad], {:name => "asd"})[0].new_record?).must_equal(false)
end
end

describe "%before_create" do
it "calls before create" do
film = CustomFilmFactory.create
Expand Down Expand Up @@ -51,6 +97,13 @@ describe Factory::Jennifer::Base do
expect(film.new_record?).must_equal(false)
end

it "all model callbacks during creating" do
film = FilmFactory.create
expect(film.before_create).must_equal(true)
expect(film.before_save).must_equal(true)
expect(film.after_initialize).must_equal(true)
end

describe "ancestor factory" do
it "accepts no arguments" do
film = CustomFilmFactory.create
Expand Down
15 changes: 13 additions & 2 deletions spec/support/factories.cr
Expand Up @@ -68,7 +68,6 @@ class TestFactory < Factory::Base
end

trait :addon do
attr :f1, "addon1"
end
end

Expand All @@ -77,7 +76,6 @@ class SecondTestFactory < TestFactory
assign :f3, 0.64

trait :nested do
attr :f1, "nested"
assign :f2, -2
assign :f4, "nestedaddon"
end
Expand Down Expand Up @@ -120,6 +118,9 @@ end

class CustomFilmFactory < FilmFactory
sequence(:name) { |i| "Custom Film #{i}" }

association :author, AuthorFactory

after_create do |obj|
obj.name = obj.name! + "after"
end
Expand All @@ -128,3 +129,13 @@ class CustomFilmFactory < FilmFactory
obj.name = obj.name! + "before"
end
end

class FictionFilmFactory < CustomFilmFactory
trait :with_special_author do
association :author, options: {name: "Special Author"}
end
end

class AuthorFactory < Factory::Jennifer::Base
sequence(:name) { |i| "Author #{i}" }
end
18 changes: 18 additions & 0 deletions spec/support/migrations/20170822160720829_add_author.cr
@@ -0,0 +1,18 @@
class AddAuthor20170822160720829 < Jennifer::Migration::Base
def up
create_table :authors do |t|
t.string :name
t.string :last_name
end
change_table :films do |t|
t.add_column :author_id, :integer
end
end

def down
drop_table :authors
change_table :films do |t|
t.drop_column :author_id
end
end
end
23 changes: 22 additions & 1 deletion spec/support/models.cr
Expand Up @@ -3,6 +3,27 @@ class Film < Jennifer::Model::Base
id: {type: Int32, primary: true},
name: String?,
rating: Int32,
budget: Float32?
budget: Float32?,
author_id: Int32?
)

belongs_to :author, Author

{% for callback in %i(before_save after_initialize before_create) %}
getter {{callback.id}} = false

def set_{{callback.id}}
@{{callback.id}} = true
end

{{callback.id}} :set_{{callback.id}}
{% end %}
end

class Author < Jennifer::Model::Base
mapping(
id: {type: Int32, primary: true},
name: String?
)
has_many :films, Film
end
8 changes: 4 additions & 4 deletions src/factory.cr
Expand Up @@ -48,7 +48,7 @@ module Factory
\{{@type}}.build(**attrs)
end

def self.build_\{{factory_name}}(attrs : Hash)
def self.build_\{{factory_name}}(attrs : Hash | NamedTuple)
\{{@type}}.build(attrs)
end

Expand All @@ -60,7 +60,7 @@ module Factory
\{{@type}}.build(traits, **attrs)
end

def self.build_\{{factory_name}}(traits : Array, attrs : Hash)
def self.build_\{{factory_name}}(traits : Array, attrs : Hash | NamedTuple)
\{{@type}}.build(traits, attrs)
end

Expand All @@ -76,7 +76,7 @@ module Factory
arr
end

def self.build_\{{factory_name}}(count : Int32, attrs : Hash)
def self.build_\{{factory_name}}(count : Int32, attrs : Hash | NamedTuple)
arr = [] of \{{CLASS_NAME.last.id}}
count.times { arr << \{{@type}}.build(attrs) }
arr
Expand All @@ -94,7 +94,7 @@ module Factory
arr
end

def self.build_\{{factory_name}}(count : Int32, traits : Array, attrs : Hash)
def self.build_\{{factory_name}}(count : Int32, traits : Array, attrs : Hash | NamedTuple)
arr = [] of \{{CLASS_NAME.last.id}}
count.times { arr << \{{@type}}.build(traits, attrs) }
arr
Expand Down
19 changes: 11 additions & 8 deletions src/factory/base.cr
Expand Up @@ -10,7 +10,11 @@ module Factory
def self.build
end

def self.get_trait(name)
def self.get_trait(name : String, go_deep)
end

def self.get_trait(name : Symbol)
get_trait(name.to_s)
end

def self.after_initialize(obj)
Expand Down Expand Up @@ -109,12 +113,11 @@ module Factory
\{{trait.id}}
\{% end %}

def self.get_trait(name : String)
t = super
return t if t
def self.get_trait(name : String, go_deep : Bool = true)
\{% for k, v in TRAITS %}
return \{{v.id}} if \{{k}} == name
\{% end %}
super if go_deep
end

\{% if ARGUMENT_TYPE.size == 0 && @type.superclass.constant("IS_FACTORY")[-1] == "true" && @type.superclass.constant("ARGUMENT_TYPE").size != 0 %}
Expand Down Expand Up @@ -158,13 +161,13 @@ module Factory
obj
end

def self.build(attrs : Hash)
def self.build(attrs : Hash | NamedTuple)
obj = initialize_with(build_attributes(attrs), [] of String)
after_initialize(obj)
obj
end

def self.build(traits : Array, attrs : Hash)
def self.build(traits : Array, attrs : Hash | NamedTuple)
obj = initialize_with(build_attributes(attrs, traits), traits)
after_initialize(obj)
obj
Expand Down Expand Up @@ -194,7 +197,7 @@ module Factory
attrs = attributes
traits.each do |name|
trait = get_trait(name.to_s)
raise "Unknown trait" if trait.nil?
raise "Unknown trait \"#{name.to_s}\"" if trait.nil?
trait.not_nil!.add_attributes(attrs)
end
opts.each do |k, v|
Expand All @@ -205,7 +208,7 @@ module Factory

def self.make_assigns(obj, traits)
\{% for k, v in ASSIGNS %}
obj.\{{k.id}} = \{% if v =~ /->/ %} \{{v.id}}.call \{% else %} @@assign_\{{k.id}} \{% end %}
obj.\{{k.id}} = \{% if v =~ /->/ %} \{{v.id}}.call \{% else %} @@assign_\{{k.id}} \{% end %}
\{% end %}
traits.each do |name|
trait = get_trait(name.to_s)
Expand Down
3 changes: 2 additions & 1 deletion src/factory/jennifer.cr
@@ -1 +1,2 @@
require "./jennifer/*"
require "./jennifer/base"
require "./jennifer/trait"

0 comments on commit 39256d6

Please sign in to comment.