ninja-model 0.8.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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