factory_boy 1.0.5 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +68 -31
- data/lib/blank_slate.rb +3 -0
- data/lib/plant.rb +37 -46
- data/lib/query.rb +44 -0
- data/lib/reflection.rb +40 -0
- data/lib/selector.rb +181 -0
- data/lib/setup.rb +13 -22
- data/lib/stubber.rb +164 -21
- data/test/Rakefile.rb +11 -0
- data/test/app/models/address.rb +9 -0
- data/test/app/models/customer.rb +3 -0
- data/test/app/models/profile.rb +2 -0
- data/test/app/models/user.rb +7 -0
- data/test/databases.rake.rb +513 -0
- data/test/db/migrate/20101230223546_create_users.rb.rb +14 -0
- data/test/db/migrate/20101230223547_create_profiles.rb +15 -0
- data/test/db/migrate/20101230223548_create_customers.rb +11 -0
- data/test/db/migrate/20101230223549_create_addresses.rb +13 -0
- data/test/db/schema.rb +41 -0
- data/test/help_test.rb +15 -5
- data/test/plants.rb +5 -5
- data/test/test_basic_queries.rb +36 -0
- data/test/test_plant_definition.rb +129 -0
- data/test/test_plants_ids.rb +16 -0
- data/test/test_queries_on_has_many_association.rb +51 -0
- data/test/test_queries_on_has_one_association.rb +45 -0
- data/test/test_queries_on_model_attributes.rb +59 -0
- data/test/test_queries_with_like.rb +22 -0
- data/test/test_queries_with_limit.rb +28 -0
- data/test/test_queries_with_named_scope.rb +18 -0
- data/test/test_queries_with_order.rb +17 -0
- data/test/test_queries_with_ranges.rb +21 -0
- data/test/test_selector_condition.rb +26 -0
- data/test/test_stubbing.rb +43 -0
- metadata +60 -22
- data/test/models/adress.rb +0 -12
- data/test/models/customer.rb +0 -7
- data/test/models/profile.rb +0 -8
- data/test/models/user.rb +0 -8
- data/test/plant_tests.rb +0 -115
- data/test/test_plant.rb +0 -7
- data/test/test_plant_with_active_support.rb +0 -8
data/README.rdoc
CHANGED
@@ -1,29 +1,64 @@
|
|
1
1
|
== Overview
|
2
|
-
|
3
|
-
|
4
|
-
ActiveRecord::Base
|
5
|
-
|
6
|
-
|
7
|
-
|
2
|
+
Factory Boy aims to avoid slow unit tests due to usage of create/find fixtures in database, with factory_girl for example.
|
3
|
+
Factory Boy can be used as factory_girl except that factories are not created in database.
|
4
|
+
ActiveRecord::Base finders method is stubbed to return fixtures (plants) you have instanciate.
|
5
|
+
|
6
|
+
Now, Factory Boy 2 handle stub of Active Record (3+) queries.
|
7
|
+
This means, the fixtures(plants) created with factory boy are retrieved via a AR queries(and only with AR new queries) of your models.
|
8
|
+
It does not pretend to stub 100% of all queries, but the coverage can be estimated at about 80%-90% of useful queries.
|
9
|
+
|
10
|
+
|
11
|
+
Active Record is stubbed only when at least one Plant is created in a test.
|
12
|
+
After each test everything is unstubbed.
|
13
|
+
That means, if you have a case where a particular(complex) query is executed but not right stubbed with factory boy you can test using fixtures in databases(with factory girl or just model.create ..), skipping factory boy process.
|
14
|
+
|
15
|
+
Tested with Active Record 3.0.1
|
16
|
+
Tests are suppose to use ActiveSupport::TestCase
|
8
17
|
|
9
18
|
See some examples below.
|
10
|
-
You
|
19
|
+
You should see unit tests to inspect tested stubbed queries!
|
11
20
|
|
12
|
-
== Install
|
13
21
|
|
14
|
-
|
22
|
+
== Queries it supposes to handle
|
23
|
+
|
24
|
+
- where clauses on attributes and associations
|
25
|
+
- chained where clauses
|
26
|
+
- like sql predicate
|
27
|
+
- limit, offset
|
28
|
+
- order (with only one order clause)
|
29
|
+
- ranges (ie where(:age => (20..30)))
|
30
|
+
- IS NULL and IS NOT NULL sql predicates
|
31
|
+
|
32
|
+
The better way to see queries handled is to see all unit tests.
|
33
|
+
|
34
|
+
== Queries NOT handled
|
35
|
+
|
36
|
+
- Queries with explicit sql string(find_by_sql("..."))
|
37
|
+
- #order with more than one order clause (ie .order(name asc, age desc))
|
38
|
+
- IS and IS NOT with other operand than NULL
|
39
|
+
|
40
|
+
== Ids
|
15
41
|
|
42
|
+
Each plant fixture has now an (unique) id.
|
16
43
|
|
17
|
-
==
|
44
|
+
== Usage
|
18
45
|
|
19
|
-
Define your Plants (
|
46
|
+
Define your Plants (~ Factories if factory_girl) in test/plants.rb
|
20
47
|
|
21
48
|
Example :
|
22
49
|
|
50
|
+
Plant.define :address do |address|
|
51
|
+
address.number = 12
|
52
|
+
address.street = "rue de Brest"
|
53
|
+
end
|
54
|
+
|
23
55
|
Plant.define :user do |user|
|
24
56
|
user.name="Bart"
|
25
57
|
user.age=800
|
58
|
+
user.addresses = [Plant(:address)]
|
26
59
|
end
|
60
|
+
|
61
|
+
|
27
62
|
|
28
63
|
Get it with :
|
29
64
|
|
@@ -34,10 +69,16 @@ Get it with :
|
|
34
69
|
|
35
70
|
|
36
71
|
def test___1
|
37
|
-
|
72
|
+
address = Plant(:address, :street => 'rue des Lilas')
|
73
|
+
user = Plant(:user, :name => 'Joe', :addresses => [address])
|
74
|
+
|
38
75
|
assert_equal user, User.find #OK
|
39
76
|
assert_equal user, User.find(:first) #OK
|
40
77
|
assert_equal user, User.find(:last) #OK
|
78
|
+
assert_equal [user], User.where(:name => 'Joe') #OK
|
79
|
+
assert_equal [user], User.where("name = 'Joe' and addresses.street = 'rue des Lilas'").joins(':addresses) #OK
|
80
|
+
assert_equal [address], user.addresses.where(:street => 'rue des Lilas') #OK
|
81
|
+
|
41
82
|
end
|
42
83
|
|
43
84
|
|
@@ -51,6 +92,7 @@ Get it with :
|
|
51
92
|
|
52
93
|
user = Plant(:user, :name => "Marie", :age => age)
|
53
94
|
|
95
|
+
|
54
96
|
== Specification of the class of the fixture definition
|
55
97
|
|
56
98
|
Plant.define :admin, :class => User do |user|
|
@@ -66,16 +108,16 @@ Assign fixtures to association in definition of plant :
|
|
66
108
|
profile.password = "BREIZH!"
|
67
109
|
end
|
68
110
|
|
69
|
-
Plant.define :
|
70
|
-
|
71
|
-
|
111
|
+
Plant.define :address do |address|
|
112
|
+
address.number = 12
|
113
|
+
address.street = "rue de Brest"
|
72
114
|
end
|
73
115
|
|
74
116
|
Plant.define :user do |user|
|
75
117
|
user.name = "Bart"
|
76
118
|
user.age = 800
|
77
|
-
user.profile = Plant
|
78
|
-
user.adresses = [Plant
|
119
|
+
user.profile = Plant(:profile)
|
120
|
+
user.adresses = [Plant(:address)]
|
79
121
|
end
|
80
122
|
|
81
123
|
|
@@ -86,7 +128,7 @@ If you want to use the value of another attribute in definition, do like that :
|
|
86
128
|
|
87
129
|
Plant.define :user do |user|
|
88
130
|
user.name = "Marie"
|
89
|
-
user.adresses = [
|
131
|
+
user.adresses = [Plant(:address, :street => "Rue de #{user.name}")]
|
90
132
|
end
|
91
133
|
|
92
134
|
|
@@ -102,26 +144,21 @@ As with factory_girl you are able to use sequences, like that :
|
|
102
144
|
Plant.next(:email) # => "incognito2@kantena.com"
|
103
145
|
|
104
146
|
|
105
|
-
== Dependencies
|
106
|
-
|
107
|
-
No dependency.
|
108
|
-
Doesn't work, for now, with Active Support 3.0 (In development)
|
109
147
|
|
110
148
|
== In Development
|
111
149
|
|
112
|
-
- Stubs
|
113
|
-
- Plant(:xx).clear
|
114
|
-
- Provide an id to each Plant
|
115
|
-
- When assign instance to an association(ie profile of user), set foreign_key id (ie profile_id of user)
|
116
|
-
- To work with Rails 3 (Active Support 3)
|
150
|
+
- Stubs aggregations methods in queries(sum, count ...)
|
117
151
|
|
118
|
-
== Change Log
|
119
152
|
|
120
|
-
|
121
|
-
|
153
|
+
== Install
|
154
|
+
|
155
|
+
gem install factory_boy
|
156
|
+
|
157
|
+
|
158
|
+
== Change Log
|
122
159
|
|
123
|
-
== Notes
|
124
160
|
|
125
161
|
|
162
|
+
== Issues
|
126
163
|
|
127
164
|
<b>Report Bugs here , on github</b>
|
data/lib/blank_slate.rb
ADDED
data/lib/plant.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'active_support/inflector'
|
3
1
|
require 'stubber'
|
2
|
+
require 'selector'
|
4
3
|
require 'setup'
|
4
|
+
require 'query'
|
5
|
+
require 'reflection'
|
5
6
|
|
6
7
|
module Plant
|
7
8
|
|
@@ -9,83 +10,73 @@ module Plant
|
|
9
10
|
@@pool = {}
|
10
11
|
@@map = {}
|
11
12
|
@@sequences = {}
|
12
|
-
|
13
|
-
|
13
|
+
@@id = 0
|
14
|
+
|
14
15
|
def self.define symbol, args={}
|
15
16
|
klass = args[:class] || symbol.to_s.camelize.constantize
|
16
|
-
|
17
|
-
yield
|
18
|
-
add_plant(klass,
|
19
|
-
add_plant(symbol,
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
def self.stubs klass
|
24
|
-
unless @@stubbed.include?(klass)
|
25
|
-
Plant::Stubber.stubs_find(klass)
|
26
|
-
@@stubbed << klass
|
27
|
-
end
|
17
|
+
definition = klass.new
|
18
|
+
yield definition if block_given?
|
19
|
+
add_plant(klass, definition)
|
20
|
+
add_plant(symbol, definition) if args[:class]
|
21
|
+
Plant::Reflection.reflect(klass)
|
22
|
+
Plant::Stubber.stubs
|
28
23
|
end
|
29
|
-
|
30
|
-
def self.unstub_find_for_each_class
|
31
|
-
@@stubbed.each {|klass| Plant::Stubber.unstubs_find_for(klass)}
|
32
|
-
@@stubbed = []
|
33
|
-
end
|
34
|
-
|
35
|
-
def self.all
|
36
|
-
@@pool
|
37
|
-
end
|
38
|
-
|
24
|
+
|
39
25
|
def self.add_plant klass, instance
|
40
26
|
@@plants[klass] = instance
|
41
27
|
end
|
42
|
-
|
28
|
+
|
43
29
|
def self.plants
|
44
30
|
@@plants
|
45
31
|
end
|
46
32
|
|
47
|
-
def self.
|
33
|
+
def self.all
|
34
|
+
@@pool
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.reload
|
48
38
|
load "#{RAILS_ROOT}/test/plants.rb" if Plant.plants.empty?
|
49
39
|
end
|
50
|
-
|
40
|
+
|
51
41
|
def self.pool symbol
|
52
|
-
|
53
|
-
object =
|
42
|
+
definition = plants[symbol] || plants[symbol.to_s.camelize.constantize]
|
43
|
+
object = Plant::Reflection.clone(definition,@@id += 1)
|
54
44
|
yield object if block_given?
|
55
|
-
@@pool[
|
56
|
-
@@pool[
|
45
|
+
@@pool[definition.class] ||= []
|
46
|
+
@@pool[definition.class] << object
|
57
47
|
object
|
58
48
|
end
|
59
|
-
|
49
|
+
|
60
50
|
def self.destroy
|
61
51
|
@@pool = {}
|
62
52
|
@@plants = {}
|
63
53
|
@@map = {}
|
64
54
|
@@sequences = {}
|
65
|
-
|
55
|
+
@@id = 0
|
66
56
|
end
|
67
|
-
|
57
|
+
|
68
58
|
def self.sequence symbol, &proc
|
69
59
|
@@sequences[symbol] = {:lambda => proc, :index => 0}
|
70
60
|
end
|
71
|
-
|
61
|
+
|
72
62
|
def self.next symbol
|
73
63
|
@@sequences[symbol][:lambda].call(@@sequences[symbol][:index] += 1)
|
74
64
|
end
|
75
65
|
|
76
|
-
def self.association
|
77
|
-
Plant.
|
66
|
+
def self.set_foreign_keys object, association, value
|
67
|
+
fk_setter = lambda {|value| value.send(Plant::Reflection.foreign_key(object, association) + '=', object.id)}
|
68
|
+
|
69
|
+
case
|
70
|
+
when Plant::Reflection.has_many_association?(object.class, association) then value.each {|v| fk_setter.call(v)}
|
71
|
+
when value && Plant::Reflection.has_one_association?(object.class, association) then fk_setter.call(value)
|
72
|
+
end
|
78
73
|
end
|
79
|
-
|
74
|
+
|
80
75
|
end
|
81
76
|
|
82
77
|
def Plant symbol, args={}
|
83
|
-
Plant.reload
|
78
|
+
Plant.reload
|
84
79
|
Plant.pool(symbol) do |instance|
|
85
|
-
args.each {|key, value| instance.send(key.to_s + '=', value)}
|
80
|
+
args.each {|key, value| Plant.set_foreign_keys(instance, key, value); instance.send(key.to_s + '=', value)}
|
86
81
|
end
|
87
82
|
end
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
data/lib/query.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
module Plant
|
2
|
+
module Query
|
3
|
+
|
4
|
+
@@wheres = nil
|
5
|
+
|
6
|
+
def self.wheres= wheres
|
7
|
+
@@wheres = wheres
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.wheres
|
11
|
+
@@wheres
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.find_all klass
|
15
|
+
Plant.all[klass] || []
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.find_by_ids klass, ids
|
19
|
+
plants = Plant.all[klass].select{|plant| ids.include?(plant.id)}
|
20
|
+
return plants.first if plants.size == 1
|
21
|
+
plants
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.select klass
|
25
|
+
Plant::Selector.new(:klass => klass, :wheres => @@wheres, :plants => Plant.all[klass].to_a).select
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.order objects, args
|
29
|
+
attribute, order = args.split(" ")
|
30
|
+
objects.sort {|x, y| (x,y = y,x if order == 'desc'); x.send(attribute.to_sym) <=> y.send(attribute.to_sym)}
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.limit objects, limit_value
|
34
|
+
@@objects = objects
|
35
|
+
@@limit = limit_value
|
36
|
+
objects.first(limit_value)
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.offset objects, offset_value
|
40
|
+
@@objects[offset_value..-1].first(@@limit)
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
data/lib/reflection.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
module Plant
|
2
|
+
module Reflection
|
3
|
+
@@reflections = {}
|
4
|
+
|
5
|
+
def self.reflect klass
|
6
|
+
return if @@reflections[klass]
|
7
|
+
associations = lambda {|klass, macro| klass.reflect_on_all_associations(macro).map{|association| association.name} || []}
|
8
|
+
|
9
|
+
reflection = @@reflections[klass] = Hash.new
|
10
|
+
[:has_many, :has_one, :belongs_to].each {|relation| reflection[relation] = associations.call(klass, relation)}
|
11
|
+
reflection[:attributes] = klass.new.attributes
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.clone object, id
|
15
|
+
clone = object.class.new
|
16
|
+
cloner = Proc.new {|attribute| clone.send(attribute.to_s + "=", object.send(attribute))}
|
17
|
+
reflection = @@reflections[object.class]
|
18
|
+
|
19
|
+
clone.id = id
|
20
|
+
reflection[:attributes].keys.each(&cloner)
|
21
|
+
[:has_many, :has_one, :belongs_to].each {|relation| reflection[relation].each(&cloner)}
|
22
|
+
|
23
|
+
clone
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.has_many_association? klass, method
|
27
|
+
@@reflections[klass][:has_many].include?(method)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.has_one_association? klass, method
|
31
|
+
@@reflections[klass][:has_one].include?(method)
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.foreign_key object, association
|
36
|
+
object.class.name.underscore + '_id'
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
data/lib/selector.rb
ADDED
@@ -0,0 +1,181 @@
|
|
1
|
+
require 'blank_slate'
|
2
|
+
|
3
|
+
module Plant
|
4
|
+
|
5
|
+
class Selector
|
6
|
+
|
7
|
+
class Condition
|
8
|
+
|
9
|
+
def initialize wheres, klass
|
10
|
+
@wheres = wheres
|
11
|
+
@klass = klass
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_ruby
|
15
|
+
@wheres.inject("") do |sql, where|
|
16
|
+
copy = where.clone
|
17
|
+
|
18
|
+
copy.gsub!(/\s=\s/, " == ")
|
19
|
+
copy.gsub!('"','')
|
20
|
+
|
21
|
+
copy.gsub!(/\s<>\s/, " != ")
|
22
|
+
copy.gsub!('"','')
|
23
|
+
|
24
|
+
copy.match(/(\sLIKE\s*)'/i)
|
25
|
+
copy.gsub!($1,'.match ') if $1
|
26
|
+
|
27
|
+
copy.match(/(\sIN\s*)\(/i)
|
28
|
+
copy.gsub!($1,'.included_in?') if $1
|
29
|
+
|
30
|
+
copy.match(/(\sBETWEEN\s*)((\d*)\sAND\s(\d*))/i)
|
31
|
+
between, range = $1, ($3.to_i .. $4.to_i)
|
32
|
+
copy.gsub!($2, '') if $2
|
33
|
+
copy.gsub!(between, ".included_in?(#{range}) ") if between
|
34
|
+
|
35
|
+
copy.match(/(\sIS\sNULL)/i)
|
36
|
+
copy.gsub!($1,'== nil') if $1
|
37
|
+
|
38
|
+
copy.match(/(\sIS\sNOT\sNULL)/i)
|
39
|
+
copy.gsub!($1,'!= nil') if $1
|
40
|
+
|
41
|
+
|
42
|
+
sql << (sql.blank? ? "" : " and ") + copy
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class ArrayCollection < BlankSlate
|
48
|
+
|
49
|
+
def initialize collection
|
50
|
+
@collection = collection
|
51
|
+
end
|
52
|
+
|
53
|
+
def compare operator, operand
|
54
|
+
collection = @collection.dup
|
55
|
+
collection.map{|object| Attribute.new(object, @method)}.any? {|object| object.send(operator, operand) }
|
56
|
+
end
|
57
|
+
|
58
|
+
def method_missing method, *args, &block
|
59
|
+
if (@method)
|
60
|
+
return compare(method, *args)
|
61
|
+
end
|
62
|
+
@method = method
|
63
|
+
self
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class Association
|
68
|
+
|
69
|
+
def initialize(association)
|
70
|
+
@association = association
|
71
|
+
end
|
72
|
+
|
73
|
+
def method_missing method, *args, &block
|
74
|
+
return nil unless @association
|
75
|
+
Attribute.new(@association, method)
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
class Attribute
|
81
|
+
|
82
|
+
def initialize reference, method=nil
|
83
|
+
@reference = reference
|
84
|
+
@method = method
|
85
|
+
end
|
86
|
+
|
87
|
+
def compare operator, operand
|
88
|
+
value = @reference.send(@method)
|
89
|
+
operand = type_cast(operand, value)
|
90
|
+
value.send(operator, operand)
|
91
|
+
end
|
92
|
+
|
93
|
+
def type_cast operand, value
|
94
|
+
case value
|
95
|
+
when TrueClass, FalseClass : (operand == 't' || operand == '1')
|
96
|
+
else operand
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def == operand
|
101
|
+
compare(:==, operand)
|
102
|
+
end
|
103
|
+
|
104
|
+
def > operand
|
105
|
+
compare(:>, operand)
|
106
|
+
end
|
107
|
+
|
108
|
+
def < operand
|
109
|
+
compare(:<, operand)
|
110
|
+
end
|
111
|
+
|
112
|
+
def >= operand
|
113
|
+
compare(:>=, operand)
|
114
|
+
end
|
115
|
+
|
116
|
+
def <= operand
|
117
|
+
compare(:<=, operand)
|
118
|
+
end
|
119
|
+
|
120
|
+
def match operand
|
121
|
+
operand.gsub!(/\%/,'(.*)')
|
122
|
+
operand = '^' + operand + '$'
|
123
|
+
@reference.send(@method).match(operand)
|
124
|
+
end
|
125
|
+
|
126
|
+
def included_in? *operand
|
127
|
+
range = operand.first if operand.first.is_a?(Range)
|
128
|
+
return range.include?(@reference.send(@method)) if range
|
129
|
+
operand.include?(@reference.send(@method))
|
130
|
+
end
|
131
|
+
|
132
|
+
def method_missing method, *args, &block
|
133
|
+
@method = method
|
134
|
+
self
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
|
139
|
+
def initialize opt={}
|
140
|
+
@wheres = opt[:wheres]
|
141
|
+
@plants = opt[:plants]
|
142
|
+
@klass = opt[:klass]
|
143
|
+
end
|
144
|
+
|
145
|
+
def select
|
146
|
+
condition = Condition.new(@wheres, @klass)
|
147
|
+
|
148
|
+
Plant::Stubber.stubs_associations_collections
|
149
|
+
Plant::Stubber.stubs_attribute_methods
|
150
|
+
objects = @plants.select {|object| @binding = binding(); eval("#{condition.to_ruby}")}
|
151
|
+
Plant::Stubber.unstubs_associations_collections
|
152
|
+
Plant::Stubber.unstubs_attribute_methods
|
153
|
+
|
154
|
+
objects
|
155
|
+
end
|
156
|
+
|
157
|
+
private
|
158
|
+
|
159
|
+
def method_missing method, *args, &block
|
160
|
+
case
|
161
|
+
when has_one_association?(method) then Association.new(eval("object.#{method.to_s[0..-2]}", @binding))
|
162
|
+
when has_many_association?(method) then ArrayCollection.new(eval("object.#{method}", @binding))
|
163
|
+
when self_reference?(method) then Attribute.new(eval("object", @binding))
|
164
|
+
else Attribute.new(eval("object", @binding), method)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def self_reference? method
|
169
|
+
@klass.name.downcase == method.to_s[0..-2]
|
170
|
+
end
|
171
|
+
|
172
|
+
def has_one_association? method
|
173
|
+
@klass.new.respond_to?(method.to_s[0..-2])
|
174
|
+
end
|
175
|
+
|
176
|
+
def has_many_association? method
|
177
|
+
Plant::Reflection.has_many_association?(@klass, method)
|
178
|
+
end
|
179
|
+
|
180
|
+
end
|
181
|
+
end
|