ninja-model 0.8.1 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. data/.gitignore +1 -1
  2. data/Gemfile +1 -0
  3. data/Rakefile +6 -0
  4. data/lib/ninja_model.rb +23 -7
  5. data/lib/ninja_model/adapters.rb +25 -20
  6. data/lib/ninja_model/adapters/adapter_manager.rb +2 -0
  7. data/lib/ninja_model/adapters/adapter_pool.rb +2 -0
  8. data/lib/ninja_model/associations.rb +63 -101
  9. data/lib/ninja_model/associations/association.rb +146 -0
  10. data/lib/ninja_model/associations/association_scope.rb +39 -0
  11. data/lib/ninja_model/associations/belongs_to_association.rb +33 -21
  12. data/lib/ninja_model/associations/builder/association.rb +57 -0
  13. data/lib/ninja_model/associations/builder/belongs_to.rb +33 -0
  14. data/lib/ninja_model/associations/builder/collection_association.rb +60 -0
  15. data/lib/ninja_model/associations/builder/has_many.rb +11 -0
  16. data/lib/ninja_model/associations/builder/has_one.rb +20 -0
  17. data/lib/ninja_model/associations/builder/singular_association.rb +49 -0
  18. data/lib/ninja_model/associations/collection_association.rb +103 -0
  19. data/lib/ninja_model/associations/collection_proxy.rb +45 -0
  20. data/lib/ninja_model/associations/has_many_association.rb +19 -43
  21. data/lib/ninja_model/associations/has_one_association.rb +52 -6
  22. data/lib/ninja_model/associations/singular_association.rb +61 -0
  23. data/lib/ninja_model/attribute.rb +5 -2
  24. data/lib/ninja_model/attribute_methods.rb +35 -40
  25. data/lib/ninja_model/base.rb +31 -39
  26. data/lib/ninja_model/identity.rb +8 -6
  27. data/lib/ninja_model/rails_ext/active_record.rb +69 -225
  28. data/lib/ninja_model/railtie.rb +0 -9
  29. data/lib/ninja_model/reflection.rb +103 -20
  30. data/lib/ninja_model/relation.rb +2 -2
  31. data/lib/ninja_model/relation/query_methods.rb +16 -0
  32. data/lib/ninja_model/version.rb +1 -1
  33. data/ninja-model.gemspec +2 -1
  34. data/spec/db/schema.rb +15 -0
  35. data/spec/{ninja_model → lib/ninja_model}/adapters/abstract_adapter_spec.rb +0 -0
  36. data/spec/lib/ninja_model/adapters/adapter_manager_spec.rb +72 -0
  37. data/spec/lib/ninja_model/adapters/adapter_pool_spec.rb +230 -0
  38. data/spec/lib/ninja_model/adapters_spec.rb +120 -0
  39. data/spec/lib/ninja_model/associations/belongs_to_association_spec.rb +76 -0
  40. data/spec/lib/ninja_model/associations/has_many_association_spec.rb +118 -0
  41. data/spec/lib/ninja_model/associations/has_one_association_spec.rb +80 -0
  42. data/spec/{ninja_model → lib/ninja_model}/attribute_methods_spec.rb +2 -2
  43. data/spec/{ninja_model → lib/ninja_model}/attribute_spec.rb +4 -0
  44. data/spec/{ninja_model → lib/ninja_model}/base_spec.rb +0 -0
  45. data/spec/{ninja_model → lib/ninja_model}/identity_spec.rb +0 -0
  46. data/spec/{ninja_model → lib/ninja_model}/persistence_spec.rb +0 -0
  47. data/spec/{ninja_model → lib/ninja_model}/predicate_spec.rb +0 -0
  48. data/spec/{ninja_model → lib/ninja_model}/query_methods_spec.rb +0 -0
  49. data/spec/{ninja_model → lib/ninja_model}/reflection_spec.rb +7 -9
  50. data/spec/{ninja_model → lib/ninja_model}/relation_spec.rb +0 -0
  51. data/spec/{ninja_model → lib/ninja_model}/symbol_spec.rb +0 -0
  52. data/spec/{ninja_model → lib/ninja_model}/validation_spec.rb +0 -0
  53. data/spec/{ninja_model_spec.rb → lib/ninja_model_spec.rb} +0 -0
  54. data/spec/models/bio.rb +8 -0
  55. data/spec/models/body.rb +7 -0
  56. data/spec/models/category.rb +7 -0
  57. data/spec/models/email_address.rb +3 -0
  58. data/spec/models/post.rb +13 -0
  59. data/spec/models/tag.rb +3 -0
  60. data/spec/models/user.rb +4 -0
  61. data/spec/spec_helper.rb +38 -5
  62. data/spec/support/dummy_adapter/adapter.rb +48 -0
  63. data/spec/support/factories/bio.rb +11 -0
  64. data/spec/support/factories/body.rb +12 -0
  65. data/spec/support/factories/email_address.rb +5 -0
  66. data/spec/support/factories/post.rb +12 -0
  67. data/spec/support/factories/tag.rb +5 -0
  68. data/spec/support/factories/user.rb +5 -0
  69. metadata +121 -63
  70. data/spec/ninja_model/adapters/adapter_manager_spec.rb +0 -69
  71. data/spec/ninja_model/adapters/adapter_pool_spec.rb +0 -230
  72. data/spec/ninja_model/adapters_spec.rb +0 -85
@@ -1,11 +1,13 @@
1
1
  module NinjaModel
2
- class Base
3
- class_attribute :primary_key
4
- self.primary_key = :id
5
- undef_method(:id) if method_defined?(:id)
6
- end
7
-
8
2
  module Identity
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ class_attribute :primary_key
7
+ self.primary_key = :id
8
+ undef_method(:id) if method_defined?(:id)
9
+ end
10
+
9
11
  def to_model
10
12
  self
11
13
  end
@@ -1,58 +1,42 @@
1
- require 'active_record'
2
-
3
1
  module ActiveRecord
4
- class Base
5
- class << self
6
- def has_one_with_ninja_model(association_id, options = {})
7
- klass = options[:class_name] || association_id
8
- klass = klass.to_s.camelize
9
- klass = klass.singularize
10
- klass = compute_type(klass)
11
- if NinjaModel.ninja_model?(klass)
12
- ninja_proxy.handle_association(:has_one, association_id, options.merge({:class_name => klass.name}))
13
- else
14
- has_one_without_ninja_model(association_id, options)
15
- end
16
- end
17
- alias_method_chain :has_one, :ninja_model
18
-
19
- def has_many_with_ninja_model(association_id, options = {})
20
- klass = options[:class_name] || association_id
21
- klass = klass.to_s.camelize
22
- klass = klass.singularize
23
- klass = compute_type(klass)
24
- if NinjaModel.ninja_model?(klass)
25
- ninja_proxy.handle_association(:has_many, association_id, options.merge({:class_name => klass.name}))
26
- else
27
- has_many_without_ninja_model(association_id, options)
2
+ module NinjaModelExtensions
3
+ module ReflectionExt
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ def create_reflection(macro, name, options, active_record)
8
+ klass = options[:class_name] || name
9
+ klass = klass.to_s.camelize
10
+ klass = klass.singularize if macro.in?([:has_many])
11
+ klass = compute_type(klass)
12
+ if NinjaModel.ninja_model?(klass)
13
+ case macro
14
+ when :has_many, :belongs_to, :has_one
15
+ reflection = NinjaModel::Reflection::AssociationReflection.new(macro, name, options, active_record)
16
+ else
17
+ raise NotImplementedError, "NinjaModel does not currently support #{macro} associations."
18
+ end
19
+ self.reflections = self.reflections.merge(name => reflection)
20
+ reflection
21
+ else
22
+ super
23
+ end
28
24
  end
29
- end
30
- alias_method_chain :has_many, :ninja_model
31
25
 
32
- def reflect_on_association_with_ninja_model(association)
33
- if read_inheritable_attribute(:ninja_proxy) && ninja_proxy.proxy_klass.reflections.include?(association)
34
- ninja_proxy.proxy_klass.reflect_on_association(association)
35
- else
36
- reflect_on_association_without_ninja_model(association)
26
+ def reflect_on_aggregation(aggregation)
27
+ if reflections[aggregation].is_a?(NinjaModel::Reflection::AggregateReflection)
28
+ reflections[aggregation]
29
+ else
30
+ super
31
+ end
37
32
  end
38
- end
39
-
40
- alias_method_chain :reflect_on_association, :ninja_model
41
33
 
42
- def ninja_proxy
43
- read_inheritable_attribute(:ninja_proxy) || write_inheritable_attribute(:ninja_proxy, NinjaModel::Associations::NinjaModelProxy.new(self))
44
- end
45
-
46
- end
47
-
48
- def method_missing(method, *args)
49
- begin
50
- super
51
- rescue NoMethodError => ex
52
- if self.class.read_inheritable_attribute(:ninja_proxy) && ninja_proxy.respond_to?(method)
53
- ninja_proxy.send(method, *args)
54
- else
55
- raise ex
34
+ def reflect_on_association(association)
35
+ if reflections[association].is_a?(NinjaModel::Reflection::AssociationReflection)
36
+ reflections[association]
37
+ else
38
+ super
39
+ end
56
40
  end
57
41
  end
58
42
  end
@@ -60,191 +44,51 @@ module ActiveRecord
60
44
  end
61
45
 
62
46
  module NinjaModel
63
-
64
- class Base
65
- class << self
66
- def has_one_with_active_record(association_id, options = {})
67
- klass = options[:class_name] || association_id
68
- klass = klass.to_s.camelize
69
- klass = compute_type(klass)
70
- if NinjaModel.ninja_model?(klass)
71
- has_one_without_active_record(association_id, options.merge(:class_name => klass.name.underscore))
72
- else
73
- reflection = proxy.handle_association(:has_one, association_id, options)
74
- write_inheritable_hash :reflections, association_id => reflection
75
- reflection
76
- end
77
- end
78
-
79
- alias_method_chain :has_one, :active_record
80
-
81
- def belongs_to_with_active_record(association_id, options = {})
82
- klass = options[:class_name] || association_id
83
- klass = klass.to_s.camelize
84
- klass = compute_type(klass)
85
- if NinjaModel.ninja_model?(klass)
86
- belongs_to_without_active_record(association_id, options.merge(:class_name => klass.name.underscore))
87
- else
88
- reflection = proxy.handle_association(:belongs_to, association_id, options)
89
- write_inheritable_hash :reflections, association_id => reflection
90
- reflection
91
- end
92
- end
93
-
94
- alias_method_chain :belongs_to, :active_record
95
-
96
- def has_many_with_active_record(association_id, options = {})
97
- klass = options[:class_name] || association_id
98
- klass = klass.to_s.camelize.singularize
99
- klass = compute_type(klass)
100
- if NinjaModel.ninja_model?(klass)
101
- has_many_without_active_record(association_id, options.merge(:class_name => klass.name.underscore))
102
- else
103
- reflection = proxy.handle_association(:has_many, association_id, options)
104
- write_inheritable_hash :reflections, association_id => reflection
105
- reflection
106
- end
107
- end
108
-
109
- alias_method_chain :has_many, :active_record
110
-
111
- def reflect_on_association_with_active_record(association_id)
112
- if read_inheritable_attribute(:proxy) && proxy.proxy_klass.reflections.include?(association_id)
113
- proxy.proxy_klass.reflect_on_association(association_id)
114
- else
115
- reflect_on_association_without_active_record(association_id)
116
- end
117
- end
118
-
119
- alias_method_chain :reflect_on_association, :active_record
120
-
121
- def proxy
122
- read_inheritable_attribute(:proxy) || write_inheritable_attribute(:proxy, Associations::ActiveRecordProxy.new(self))
123
- end
124
- end
125
-
126
- def respond_to?(sym)
127
- if self.class.read_inheritable_attribute(:proxy) && proxy.respond_to?(sym)
128
- true
129
- else
130
- super
131
- end
132
- end
133
-
134
- def method_missing(method, *args)
135
- if self.class.read_inheritable_attribute(:proxy) && proxy.respond_to?(method)
136
- proxy.send(method, *args)
137
- else
138
- super
139
- end
140
- end
141
- end
142
-
143
- module Associations
144
- class ActiveRecordProxy
145
- attr_reader :proxy_klass
146
- def initialize(ninja_model)
147
- @klass = ninja_model
148
- @klass.class_eval do
149
- def proxy
150
- @proxy ||= begin
151
- self.class.proxy.instance(self)
47
+ module ActiveRecordExtensions
48
+ module ReflectionExt
49
+ extend ActiveSupport::Concern
50
+
51
+ module ClassMethods
52
+ def create_reflection(macro, name, options, ninja_model)
53
+ klass = options[:class_name] || name
54
+ klass = klass.to_s.camelize
55
+ klass = klass.singularize if macro.in?([:has_many])
56
+ klass = compute_type(klass)
57
+ if NinjaModel.ninja_model?(klass)
58
+ super
59
+ else
60
+ case macro
61
+ when :has_many, :belongs_to, :has_one
62
+ reflection = ActiveRecord::Reflection::AssociationReflection.new(macro, name, options, ninja_model)
63
+ when :composed_of
64
+ reflection = ActiveRecord::Reflection::AggregateReflection.new(macro, name, options, ninja_model)
65
+ else
66
+ raise NotImplementedError, "NinjaModel does not currently support #{macro} associations."
152
67
  end
153
- @proxy.attributes = self.attributes.delete_if { |k,v| k.eql?('id') }
154
- @proxy.id = self.id if self.attributes.key?(:id)
155
- @proxy
68
+ self.reflections = self.reflections.merge(name => reflection)
156
69
  end
157
70
  end
158
71
 
159
- @proxy_klass = ninja_model.parent.const_set("#{@klass.model_name.gsub(/[^a-zA-Z]/, '')}Proxy", Class.new(ActiveRecord::Base))
160
- @proxy_klass.class_eval do
161
- cattr_accessor :columns
162
- self.columns = []
163
- def self.column(name, sql_type = nil, default = nil)
164
- self.columns << ActiveRecord::ConnectionAdapters::Column.new(name, nil, sql_type.to_s, default)
165
- end
166
-
167
- def self.columns_hash
168
- h = {}
169
- self.columns.each do |c|
170
- h[c.name] = c
171
- end
172
- h
173
- end
174
- def self.column_defaults
175
- self.columns_hash
72
+ def reflect_on_aggregation(aggregation)
73
+ if reflections[aggregation].is_a?(ActiveRecord::Reflection::AggregateReflection)
74
+ reflections[aggregation]
75
+ else
76
+ super
176
77
  end
177
78
  end
178
79
 
179
- @klass.model_attributes.each do |attr|
180
- @proxy_klass.send :column, attr.name, attr.type, attr.default
181
- end
182
- end
183
-
184
- def instance(obj)
185
- proxy = @proxy_klass.new
186
- proxy.send :init_with, {'attributes' => obj.attributes}
187
- proxy
188
- end
189
-
190
- def handle_association(macro, association_id, options)
191
- unless macro.eql?(:belongs_to)
192
- options = {:foreign_key => derive_foreign_key}.merge(options)
193
- end
194
-
195
- @proxy = nil
196
- @proxy_klass.send macro, association_id, options
197
- end
198
-
199
- private
200
-
201
- def derive_foreign_key
202
- "#{@klass.name.demodulize.underscore}_id".to_sym
203
- end
204
- end
205
-
206
- class NinjaModelProxy
207
- attr_reader :proxy_klass
208
- def initialize(active_record)
209
- @klass = active_record
210
- @klass.class_eval do
211
- def ninja_proxy
212
- @ninja_proxy ||= begin
213
- self.class.ninja_proxy.instance(self)
214
- end
215
- @ninja_proxy.attributes = self.attributes
216
- @ninja_proxy
80
+ def reflect_on_association(association)
81
+ if reflections[association].is_a?(ActiveRecord::Reflection::AssociationReflection)
82
+ reflections[association]
83
+ else
84
+ super
217
85
  end
218
86
  end
219
-
220
- @proxy_klass = active_record.parent.const_set("#{@klass.model_name.demodulize}Proxy", Class.new(NinjaModel::Base))
221
-
222
- @klass.columns_hash.each_pair do |k,v|
223
- @proxy_klass.send :attribute, k, v.type, v.default, @proxy_klass
224
- end
225
- end
226
-
227
- def instance(obj)
228
- proxy = @proxy_klass.new
229
- proxy.send :instantiate, {'attributes' => obj.attributes}
230
- proxy
231
- end
232
-
233
- def handle_association(macro, association_id, options)
234
- unless macro.eql?(:belongs_to)
235
- options = {:foreign_key => derive_foreign_key}.merge(options)
236
- end
237
-
238
- @proxy = nil
239
- @proxy_klass.send macro, association_id, options
240
- end
241
-
242
- private
243
-
244
- def derive_foreign_key
245
- "#{@klass.name.demodulize.underscore}_id".to_sym
246
87
  end
247
88
  end
248
89
  end
249
-
90
+ end
91
+
92
+ ActiveSupport.on_load(:ninja_model) do
93
+ include NinjaModel::ActiveRecordExtensions::ReflectionExt
250
94
  end
@@ -12,12 +12,6 @@ module NinjaModel
12
12
  end
13
13
  end
14
14
 
15
- initializer 'ninja_model_extend_active_record' do |app|
16
- ActiveSupport.on_load(:active_record) do
17
- require 'ninja_model/rails_ext/active_record'
18
- end
19
- end
20
-
21
15
  initializer 'ninja_model_load_specs' do |app|
22
16
  config_path = File.join(app.paths['config'].to_a.first, "ninja_model.yml")
23
17
  if File.exists?(config_path)
@@ -28,8 +22,5 @@ module NinjaModel
28
22
  NinjaModel.logger.warn "[ninja-model] *WARNING* Unable to find configuration file at #{config_path}"
29
23
  end
30
24
  end
31
-
32
- config.after_initialize do |app|
33
- end
34
25
  end
35
26
  end
@@ -1,12 +1,24 @@
1
1
  module NinjaModel
2
- class Base
3
- class << self
2
+ module Reflection
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ class_attribute :reflections
7
+ self.reflections = {}
8
+ end
9
+
10
+ module ClassMethods
4
11
  def create_reflection(macro, name, options, ninja_model)
5
12
  case macro
6
13
  when :has_many, :belongs_to, :has_one
7
14
  reflection = Reflection::AssociationReflection.new(macro, name, options, ninja_model)
15
+ when :composed_of
16
+ reflection = AggregateReflection.new(macro, name, options, ninja_model)
17
+ else
18
+ raise NotImplementedError, "NinjaModel does not currently support #{macro} associations."
8
19
  end
9
- write_inheritable_hash :reflections, name => reflection
20
+
21
+ self.reflections = self.reflections.merge(name => reflection)
10
22
  reflection
11
23
  end
12
24
 
@@ -14,13 +26,14 @@ module NinjaModel
14
26
  read_inheritable_attribute(:reflections) || write_inheritable_attribute(:reflections, {})
15
27
  end
16
28
 
29
+ def reflect_on_aggregation(aggregation)
30
+ reflections[aggregation].is_a?(AggregateReflection) ? reflections[aggregation] : nil
31
+ end
32
+
17
33
  def reflect_on_association(association)
18
34
  reflections[association].is_a?(Reflection::AssociationReflection) ? reflections[association] : nil
19
35
  end
20
36
  end
21
- end
22
-
23
- module Reflection
24
37
 
25
38
  class MacroReflection
26
39
  def initialize(macro, name, options, ninja_model)
@@ -28,9 +41,11 @@ module NinjaModel
28
41
  end
29
42
 
30
43
  attr_reader :ninja_model, :name, :macro, :options
44
+ alias :active_record :ninja_model
45
+ alias :source_macro :macro
31
46
 
32
47
  def klass
33
- @klass ||= class_name.camelize.constantize
48
+ @klass ||= class_name.constantize
34
49
  end
35
50
 
36
51
  def class_name
@@ -44,33 +59,86 @@ module NinjaModel
44
59
  end
45
60
  end
46
61
 
62
+ class AggregateReflection < MacroReflection
63
+ end
64
+
47
65
  class AssociationReflection < MacroReflection
66
+ attr_accessor :original_build_association_called # :nodoc
67
+
68
+ def klass
69
+ @klass ||= ninja_model.send(:compute_type, class_name)
70
+ end
71
+
48
72
  def initialize(macro, name, options, ninja_model)
49
73
  super
50
- @collection = [:has_many].include?(macro)
74
+ @collection = macro.in?([:has_many])
75
+ end
76
+
77
+ def build_association(*options, &block)
78
+ @original_build_association_called
79
+ klass.new(*options, &block)
51
80
  end
52
81
 
53
- def build_association(*options)
54
- klass.new(*options)
82
+ def table_name
83
+ raise NotImplementedError, "table_name is not implemented in NinjaModel"
55
84
  end
56
85
 
57
- def create_association(*options)
58
- klass.create(*options)
86
+ def quoted_table_name
87
+ raise NotImplementedError, "quoted_table_name is not implemented in NinjaModel"
59
88
  end
60
89
 
61
- def create_association!(*options)
62
- klass.create!(*options)
90
+ def foreign_key
91
+ @foreign_key ||= options[:foreign_key] || derive_foreign_key
63
92
  end
64
93
 
65
94
  def primary_key_name
66
- @primary_key_name ||= options[:foreign_key] || derive_primary_key_name
67
- @primary_key_name
95
+ foreign_key
96
+ end
97
+ deprecate :primary_key_name => :foreign_key
98
+
99
+ def foreign_type
100
+ @foreign_type ||= options[:foreign_type] || "#{name}_type"
101
+ end
102
+
103
+ def type
104
+ @type ||= options[:as] && "#{options[:as]}_type"
105
+ end
106
+
107
+ def primary_key_column
108
+ @primary_key_column ||= klass.columns.find { |c| c.name == klass.primary_key }
68
109
  end
69
110
 
70
111
  def association_foreign_key
71
112
  @association_foreign_key ||= @options[:association_foreign_key] || class_name.foreign_key
72
113
  end
73
114
 
115
+ def association_primary_key(klass = nil)
116
+ options[:primary_key] || primary_key(klass || self.klass)
117
+ end
118
+
119
+ def ninja_model_primary_key
120
+ @ninja_model_primary_key ||= options[:primary_key] || primary_key(ninja_model)
121
+ end
122
+ alias :active_record_primary_key :ninja_model_primary_key
123
+
124
+ def chain
125
+ [self]
126
+ end
127
+
128
+ def conditions
129
+ [[options[:conditions]].compact]
130
+ end
131
+
132
+ def has_inverse?
133
+ @options[:inverse_of]
134
+ end
135
+
136
+ def inverse_of
137
+ if has_inverse?
138
+ @inverse_of ||= klass.reflect_on_association(options[:inverse_of])
139
+ end
140
+ end
141
+
74
142
  def collection?
75
143
  @collection
76
144
  end
@@ -79,27 +147,42 @@ module NinjaModel
79
147
  !options[:validate].nil? ? options[:validate] : (options[:autosave] == true || macro == :has_many)
80
148
  end
81
149
 
82
- private
83
-
84
150
  def belongs_to?
85
151
  macro == :belongs_to
86
152
  end
87
153
 
154
+ def association_class
155
+ case macro
156
+ when :belongs_to
157
+ Associations::BelongsToAssociation
158
+ when :has_many
159
+ Associations::HasManyAssociation
160
+ when :has_one
161
+ Associations::HasOneAssociation
162
+ end
163
+ end
164
+
165
+ private
166
+
88
167
  def derive_class_name
89
168
  class_name = name.to_s.camelize
90
169
  class_name = class_name.singularize if collection?
91
170
  class_name
92
171
  end
93
172
 
94
- def derive_primary_key_name
173
+ def derive_foreign_key
95
174
  if belongs_to?
96
175
  "#{name}_id"
97
176
  elsif options[:as]
98
177
  "#{options[:as]}_id"
99
178
  else
100
- ninja_model.name.demodulize.foreign_key
179
+ ninja_model.name.foreign_key
101
180
  end
102
181
  end
182
+
183
+ def primary_key(klass)
184
+ klass.primary_key || raise(StandardError, "Unknown primary key for #{klass}")
185
+ end
103
186
  end
104
187
  end
105
188
  end