Skip to content

Commit 2fb0d7b

Browse files
authored
Merge pull request #2 from imdrasil/add_associations
Add jennifer association support
2 parents 04159de + 39256d6 commit 2fb0d7b

11 files changed

Lines changed: 247 additions & 21 deletions

File tree

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,22 @@ It provides direct creating methods same as for building:
190190
FilmFactory.create([:bad], {:name => "Atilla"})
191191
```
192192

193+
Also any association could be described on the factory or trait level:
194+
195+
```crystal
196+
class FilmFactory < Factory::Jennifer::Base
197+
association :author
198+
association :actor, UserFactory, options: {name: "Artemius Fault"}
199+
end
200+
```
201+
202+
Allowed arguments:
203+
204+
- `:name` - first argument - represent model association name (mandatory)
205+
- `:factory` - represents factory class (optional); is defaulted from association name
206+
- `:strategy` - represents creation strategy; optional; default is "create" (also "build" is allowed)
207+
- `:options` - represents extra arguments to association factory; optional
208+
193209
## Development
194210

195211
For development postgres is required because of testing integration with Jennifer.

spec/factory/jennifer/base_spec.cr

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,52 @@ describe Factory::Jennifer::Base do
1111
::Jennifer::Adapter.adapter.rollback_transaction
1212
end
1313

14+
describe "%association" do
15+
it "uses factory's defined association" do
16+
film = Factory.create_custom_film([:bad, :hit])
17+
expect(film.author.nil?).wont_equal(true)
18+
expect(film.author!.name).must_match(/Author \d*$/)
19+
end
20+
21+
it "uses trait's author if it is given" do
22+
film = Factory.create_fiction_film([:with_special_author])
23+
expect(film.author.nil?).wont_equal(true)
24+
expect(film.author!.name).must_equal("Special Author")
25+
end
26+
27+
it "uses given overrides for factory" do
28+
film = Factory.create_fiction_film([:with_special_author])
29+
expect(film.author!.name).must_equal("Special Author")
30+
end
31+
32+
it "uses parent association if current factory has no" do
33+
film = Factory.create_fiction_film
34+
expect(film.author.nil?).must_equal(false)
35+
end
36+
37+
it "creates object without association if there is no one" do
38+
film = Factory.create_film
39+
expect(film.author.nil?).must_equal(true)
40+
end
41+
end
42+
43+
describe "%factory_creators" do
44+
it "defines all create methods on module level" do
45+
expect(Factory.create_custom_film.new_record?).must_equal(false)
46+
expect(Factory.create_custom_film(name: "New film").new_record?).must_equal(false)
47+
expect(Factory.create_custom_film({:name => "New"}).new_record?).must_equal(false)
48+
expect(Factory.create_custom_film([:bad]).new_record?).must_equal(false)
49+
expect(Factory.create_custom_film([:bad], name: "new").new_record?).must_equal(false)
50+
expect(Factory.create_custom_film([:bad], {:name => "new"}).new_record?).must_equal(false)
51+
expect(Factory.create_custom_film(1)[0].new_record?).must_equal(false)
52+
expect(Factory.create_custom_film(1, name: "asd")[0].new_record?).must_equal(false)
53+
expect(Factory.create_custom_film(1, [:bad])[0].new_record?).must_equal(false)
54+
expect(Factory.create_custom_film(1, {:name => "asd"})[0].new_record?).must_equal(false)
55+
expect(Factory.create_custom_film(1, [:bad], name: "asd")[0].new_record?).must_equal(false)
56+
expect(Factory.create_custom_film(1, [:bad], {:name => "asd"})[0].new_record?).must_equal(false)
57+
end
58+
end
59+
1460
describe "%before_create" do
1561
it "calls before create" do
1662
film = CustomFilmFactory.create
@@ -51,6 +97,13 @@ describe Factory::Jennifer::Base do
5197
expect(film.new_record?).must_equal(false)
5298
end
5399

100+
it "all model callbacks during creating" do
101+
film = FilmFactory.create
102+
expect(film.before_create).must_equal(true)
103+
expect(film.before_save).must_equal(true)
104+
expect(film.after_initialize).must_equal(true)
105+
end
106+
54107
describe "ancestor factory" do
55108
it "accepts no arguments" do
56109
film = CustomFilmFactory.create

spec/support/factories.cr

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@ class TestFactory < Factory::Base
6868
end
6969

7070
trait :addon do
71-
attr :f1, "addon1"
7271
end
7372
end
7473

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

7978
trait :nested do
80-
attr :f1, "nested"
8179
assign :f2, -2
8280
assign :f4, "nestedaddon"
8381
end
@@ -120,6 +118,9 @@ end
120118

121119
class CustomFilmFactory < FilmFactory
122120
sequence(:name) { |i| "Custom Film #{i}" }
121+
122+
association :author, AuthorFactory
123+
123124
after_create do |obj|
124125
obj.name = obj.name! + "after"
125126
end
@@ -128,3 +129,13 @@ class CustomFilmFactory < FilmFactory
128129
obj.name = obj.name! + "before"
129130
end
130131
end
132+
133+
class FictionFilmFactory < CustomFilmFactory
134+
trait :with_special_author do
135+
association :author, options: {name: "Special Author"}
136+
end
137+
end
138+
139+
class AuthorFactory < Factory::Jennifer::Base
140+
sequence(:name) { |i| "Author #{i}" }
141+
end
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
class AddAuthor20170822160720829 < Jennifer::Migration::Base
2+
def up
3+
create_table :authors do |t|
4+
t.string :name
5+
t.string :last_name
6+
end
7+
change_table :films do |t|
8+
t.add_column :author_id, :integer
9+
end
10+
end
11+
12+
def down
13+
drop_table :authors
14+
change_table :films do |t|
15+
t.drop_column :author_id
16+
end
17+
end
18+
end

spec/support/models.cr

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,27 @@ class Film < Jennifer::Model::Base
33
id: {type: Int32, primary: true},
44
name: String?,
55
rating: Int32,
6-
budget: Float32?
6+
budget: Float32?,
7+
author_id: Int32?
78
)
9+
10+
belongs_to :author, Author
11+
12+
{% for callback in %i(before_save after_initialize before_create) %}
13+
getter {{callback.id}} = false
14+
15+
def set_{{callback.id}}
16+
@{{callback.id}} = true
17+
end
18+
19+
{{callback.id}} :set_{{callback.id}}
20+
{% end %}
21+
end
22+
23+
class Author < Jennifer::Model::Base
24+
mapping(
25+
id: {type: Int32, primary: true},
26+
name: String?
27+
)
28+
has_many :films, Film
829
end

src/factory.cr

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ module Factory
4848
\{{@type}}.build(**attrs)
4949
end
5050

51-
def self.build_\{{factory_name}}(attrs : Hash)
51+
def self.build_\{{factory_name}}(attrs : Hash | NamedTuple)
5252
\{{@type}}.build(attrs)
5353
end
5454

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

63-
def self.build_\{{factory_name}}(traits : Array, attrs : Hash)
63+
def self.build_\{{factory_name}}(traits : Array, attrs : Hash | NamedTuple)
6464
\{{@type}}.build(traits, attrs)
6565
end
6666

@@ -76,7 +76,7 @@ module Factory
7676
arr
7777
end
7878

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

97-
def self.build_\{{factory_name}}(count : Int32, traits : Array, attrs : Hash)
97+
def self.build_\{{factory_name}}(count : Int32, traits : Array, attrs : Hash | NamedTuple)
9898
arr = [] of \{{CLASS_NAME.last.id}}
9999
count.times { arr << \{{@type}}.build(traits, attrs) }
100100
arr

src/factory/base.cr

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ module Factory
1010
def self.build
1111
end
1212

13-
def self.get_trait(name)
13+
def self.get_trait(name : String, go_deep)
14+
end
15+
16+
def self.get_trait(name : Symbol)
17+
get_trait(name.to_s)
1418
end
1519

1620
def self.after_initialize(obj)
@@ -109,12 +113,11 @@ module Factory
109113
\{{trait.id}}
110114
\{% end %}
111115

112-
def self.get_trait(name : String)
113-
t = super
114-
return t if t
116+
def self.get_trait(name : String, go_deep : Bool = true)
115117
\{% for k, v in TRAITS %}
116118
return \{{v.id}} if \{{k}} == name
117119
\{% end %}
120+
super if go_deep
118121
end
119122

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

161-
def self.build(attrs : Hash)
164+
def self.build(attrs : Hash | NamedTuple)
162165
obj = initialize_with(build_attributes(attrs), [] of String)
163166
after_initialize(obj)
164167
obj
165168
end
166169

167-
def self.build(traits : Array, attrs : Hash)
170+
def self.build(traits : Array, attrs : Hash | NamedTuple)
168171
obj = initialize_with(build_attributes(attrs, traits), traits)
169172
after_initialize(obj)
170173
obj
@@ -194,7 +197,7 @@ module Factory
194197
attrs = attributes
195198
traits.each do |name|
196199
trait = get_trait(name.to_s)
197-
raise "Unknown trait" if trait.nil?
200+
raise "Unknown trait \"#{name.to_s}\"" if trait.nil?
198201
trait.not_nil!.add_attributes(attrs)
199202
end
200203
opts.each do |k, v|
@@ -205,7 +208,7 @@ module Factory
205208

206209
def self.make_assigns(obj, traits)
207210
\{% for k, v in ASSIGNS %}
208-
obj.\{{k.id}} = \{% if v =~ /->/ %} \{{v.id}}.call \{% else %} @@assign_\{{k.id}} \{% end %}
211+
obj.\{{k.id}} = \{% if v =~ /->/ %} \{{v.id}}.call \{% else %} @@assign_\{{k.id}} \{% end %}
209212
\{% end %}
210213
traits.each do |name|
211214
trait = get_trait(name.to_s)

src/factory/jennifer.cr

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
require "./jennifer/*"
1+
require "./jennifer/base"
2+
require "./jennifer/trait"

0 commit comments

Comments
 (0)