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
data/.gitignore CHANGED
@@ -5,7 +5,7 @@
5
5
  tmp/**/*
6
6
  doc/*
7
7
  .DS_Store
8
- db/*.sqlite3
8
+ *.sqlite3
9
9
  doc/api
10
10
  doc/app
11
11
  doc/plugins
data/Gemfile CHANGED
@@ -4,4 +4,5 @@ gemspec
4
4
  group :test, :development do
5
5
  gem 'yard'
6
6
  gem 'simplecov', :require => false
7
+ gem 'factory_girl'
7
8
  end
data/Rakefile CHANGED
@@ -8,6 +8,12 @@ end
8
8
  require 'rspec/core/rake_task'
9
9
  require 'yard'
10
10
 
11
+ #task :prepare_db do
12
+ # db_root = File.join(File.expand_path('../', __FILE__), 'spec', 'db')
13
+ # db_file = File.join(db_root, 'test.sqlite3')
14
+ # sh "rm #{db_file}" if File.exists?(db_file)
15
+ # sh %Q{sqlite3 "#{db_file}" "create table a (a integer); drop table a;"}
16
+ #end
11
17
 
12
18
  RSpec::Core::RakeTask.new(:spec) do |t|
13
19
  t.verbose = false
data/lib/ninja_model.rb CHANGED
@@ -1,7 +1,10 @@
1
1
  require 'active_model'
2
+ require 'active_record'
2
3
  require 'active_support/core_ext'
3
4
 
4
5
  module NinjaModel
6
+ extend ActiveSupport::Autoload
7
+
5
8
  class NinjaModelError < StandardError; end
6
9
 
7
10
  class << self
@@ -12,10 +15,10 @@ module NinjaModel
12
15
  end
13
16
 
14
17
  def ninja_model?(symbol)
15
- klass = symbol.to_s.camelize
16
- klass = klass.singularize
17
- klass = klass.constantize
18
- klass.ancestors.include?(NinjaModel::Base)
18
+ #klass = symbol.to_s.camelize
19
+ #klass = klass.singularize
20
+ #klass = symbol.constantize
21
+ symbol.ancestors.include?(NinjaModel::Base)
19
22
  end
20
23
 
21
24
  def configuration
@@ -23,12 +26,25 @@ module NinjaModel
23
26
  end
24
27
  end
25
28
 
26
- class Base
29
+ autoload :Attribute
30
+ autoload :AttributeMethods
31
+ autoload :Associations
32
+ autoload :Adapters
33
+ autoload :Base
34
+ autoload :Callbacks
35
+ autoload :Identity
36
+ autoload :Persistence
37
+ autoload :Predicate
38
+ autoload :Reflection
39
+ autoload :Relation
40
+ autoload :Validation
41
+
42
+ ActiveSupport.on_load(:active_record) do
43
+ require 'ninja_model/rails_ext/active_record'
44
+ include ActiveRecord::NinjaModelExtensions::ReflectionExt
27
45
  end
28
46
  end
29
47
 
30
- require 'ninja_model/base'
31
- require 'ninja_model/core_ext/symbol'
32
48
  if defined?(Rails)
33
49
  require 'ninja_model/railtie'
34
50
  end
@@ -1,47 +1,52 @@
1
- require 'ninja_model/adapters/abstract_adapter'
2
- require 'ninja_model/adapters/adapter_manager'
3
- require 'ninja_model/adapters/adapter_pool'
4
- require 'ninja_model/adapters/adapter_specification'
5
-
6
1
  module NinjaModel
2
+ class AdapterNotSpecified < StandardError; end
3
+ class InvalidAdapter < StandardError; end
4
+ class InvalidSpecification < StandardError; end
7
5
 
8
6
  module Adapters
9
- class AdapterNotSpecified < StandardError; end
10
- class InvalidAdapter < StandardError; end
11
- class InvalidSpecification < StandardError; end
12
- end
7
+ extend ActiveSupport::Concern
13
8
 
14
- class Base
15
- class_attribute :adapter_manager
16
- self.adapter_manager = NinjaModel::Adapters::AdapterManager.new
9
+ autoload :AbstractAdapter, 'ninja_model/adapters/abstract_adapter'
10
+ autoload :AdapterManager, 'ninja_model/adapters/adapter_manager'
11
+ autoload :AdapterPool, 'ninja_model/adapters/adapter_pool'
12
+ autoload :AdapterSpecification, 'ninja_model/adapters/adapter_specification'
13
+
14
+ included do
15
+ class_attribute :adapter_manager
16
+ self.adapter_manager = NinjaModel::Adapters::AdapterManager.new
17
+ end
17
18
 
18
19
  def adapter
19
20
  self.class.retrieve_adapter
20
21
  end
21
22
 
22
- class << self
23
+ module ClassMethods
23
24
  def register_adapter(name, klass)
24
- Adapters::AdapterManager.register_adapter_class(name, klass)
25
+ if klass.ancestors.include?(NinjaModel::Adapters::AbstractAdapter)
26
+ Adapters::AdapterManager.register_adapter_class(name, klass)
27
+ else
28
+ raise InvalidAdapter, "Invalid adapter: #{klass}"
29
+ end
25
30
  end
26
31
 
27
32
  def set_adapter(spec = nil)
28
33
  case spec
29
34
  when nil
30
- raise Adapters::AdapterNotSpecified unless defined?(Rails.env)
35
+ raise AdapterNotSpecified unless defined?(Rails.env)
31
36
  set_adapter(Rails.env)
32
- when Adapters::AdapterSpecification
33
- self.adapter_manager.create_adapter(name, spec)
34
37
  when Symbol, String
35
38
  if config = NinjaModel.configuration.specs[spec.to_s]
36
39
  set_adapter(config)
37
40
  else
38
- raise Adapters::InvalidSpecification, "#{spec} is not configured"
41
+ raise InvalidSpecification, "#{spec} is not configured"
39
42
  end
43
+ when Adapters::AdapterSpecification
44
+ self.adapter_manager.create_adapter(name, spec)
40
45
  else
41
46
  spec = spec.symbolize_keys
42
- raise Adapters::AdapterNotSpecified, "configuration does not specify adapter" unless spec.key?(:adapter)
47
+ raise AdapterNotSpecified, "configuration does not specify adapter: #{spec}" unless spec.key?(:adapter)
43
48
  adapter_name = spec[:adapter]
44
- raise Adapters::InvalidAdapter, "configuration does not specify adapter" unless Adapters::AdapterManager.registered?(adapter_name)
49
+ raise InvalidAdapter, "configuration does not specify adapter" unless Adapters::AdapterManager.registered?(adapter_name)
45
50
  shutdown_adapter
46
51
  set_adapter(Adapters::AdapterSpecification.new(spec, adapter_name))
47
52
  end
@@ -1,8 +1,10 @@
1
1
  module NinjaModel
2
2
  module Adapters
3
3
  class AdapterManager
4
+
4
5
  class_attribute :registered
5
6
  self.registered = {}
7
+
6
8
  class << self
7
9
  def registered?(name)
8
10
  registered.key?(name.to_sym)
@@ -1,5 +1,7 @@
1
1
  module NinjaModel
2
+
2
3
  class ConnectionTimeoutError < NinjaModelError; end
4
+
3
5
  module Adapters
4
6
  class AdapterPool
5
7
  attr_reader :spec, :instances
@@ -1,125 +1,87 @@
1
1
  module NinjaModel
2
+ module Associations
3
+ extend ActiveSupport::Concern
2
4
 
3
- class Base
4
- class << self
5
- def has_one(association_id, options = {})
6
- reflection = create_has_one_reflection(association_id, options)
7
- association_accessor_methods(reflection, Associations::HasOneAssociation)
8
- # TODO: Implement the build/create association methods
9
- #association_constructor_method(:build, reflection, HasOneAssociation)
10
- #association_constructor_method(:create, reflection, HasOneAssociation)
11
- #configure_dependency_for_has_one(reflection)
12
- end
13
-
14
- def belongs_to(association_id, options = {})
15
- reflection = create_belongs_to_reflection(association_id, options)
16
- association_accessor_methods(reflection, Associations::BelongsToAssociation)
17
- # TODO: Implement the build/create association methods
18
- #association_constructor_method(:build, reflection, BelongsToAssociation)
19
- #association_constructor_method(:create, reflection, BelongsToAssociation)
20
- end
21
-
22
- def has_many(association_id, options = {})
23
- reflection = create_has_many_reflection(association_id, options)
24
- collection_accessor_methods(reflection, Associations::HasManyAssociation)
25
- end
26
-
27
- private
5
+ autoload :Association, 'ninja_model/associations/association'
6
+ autoload :AssociationProxy, 'ninja_model/associations/association_proxy'
7
+ autoload :AssociationScope, 'ninja_model/associations/association_scope'
8
+ autoload :BelongsToAssociation, 'ninja_model/associations/belongs_to_association'
9
+ autoload :CollectionAssociation, 'ninja_model/associations/collection_association'
10
+ autoload :CollectionProxy, 'ninja_model/associations/collection_proxy'
11
+ autoload :HasOneAssociation, 'ninja_model/associations/has_one_association'
12
+ autoload :HasManyAssociation, 'ninja_model/associations/has_many_association'
13
+ autoload :SingularAssociation, 'ninja_model/associations/singular_association'
14
+
15
+ module Builder
16
+ autoload :Association, 'ninja_model/associations/builder/association'
17
+ autoload :SingularAssociation, 'ninja_model/associations/builder/singular_association'
18
+ autoload :CollectionAssociation, 'ninja_model/associations/builder/collection_association'
19
+ autoload :HasMany, 'ninja_model/associations/builder/has_many'
20
+ autoload :BelongsTo, 'ninja_model/associations/builder/belongs_to'
21
+ autoload :HasOne, 'ninja_model/associations/builder/has_one'
22
+ end
28
23
 
29
- def create_has_one_reflection(association, options = {})
30
- create_reflection(:has_one, association, options, self)
31
- end
24
+ attr_reader :association_cache
32
25
 
33
- def create_has_many_reflection(association, options = {})
34
- create_reflection(:has_many, association, options, self)
35
- end
26
+ def association(name)
27
+ association = association_instance_get(name)
36
28
 
37
- def create_belongs_to_reflection(association, options = {})
38
- create_reflection(:belongs_to, association, options, self)
29
+ if association.nil?
30
+ reflection = self.class.reflect_on_association(name)
31
+ association = reflection.association_class.new(self, reflection)
32
+ association_instance_set(name, association)
39
33
  end
34
+ association
35
+ end
40
36
 
41
- def association_accessor_methods(reflection, association_proxy_class)
42
- redefine_method(reflection.name) do |*params|
43
- association = association_instance_get(reflection.name)
44
-
45
- if association.nil?
46
- association = association_proxy_class.new(self, reflection)
47
- retval = association.reload
48
- if retval.nil? and association_proxy_class == Associations::BelongsToAssociation
49
- association_instance_set(reflection.name, nil)
50
- return nil
51
- end
52
- association_instance_set(reflection.name, association)
53
- end
54
- association.target.nil? ? nil : association
55
- end
37
+ private
56
38
 
57
- redefine_method("loaded_#{reflection.name}?") do
58
- association = association_instance_get(reflection.name)
59
- association && association.loaded?
60
- end
61
-
62
- redefine_method("#{reflection.name}=") do |new_value|
63
- association = association_instance_get(reflection.name)
64
- if association.nil? || association.target != new_value
65
- association = association_proxy_class.new(self, reflection)
66
- end
39
+ def association_instance_get(name)
40
+ @association_cache[name.to_sym]
41
+ end
67
42
 
68
- association.replace(new_value)
69
- association_instance_set(reflection.name, new_value.nil? ? nil : association)
70
- end
43
+ def association_instance_set(name, association)
44
+ @association_cache[name.to_sym] = association
45
+ end
71
46
 
72
- redefine_method("set_#{reflection.name}_target") do |target|
73
- return if target.nil? and association_proxy_class == Associations::BelongsToAssociation
74
- association = association_proxy_class.new(self, reflection)
75
- association.target = target
76
- association_instance_set(reflection.name, association)
47
+ module ClassMethods
48
+ def add_autosave_association_callbacks(reflection)
49
+ end
50
+ def has_one(name, options = {})
51
+ klass = compute_klass(name, :has_one, options)
52
+ if NinjaModel.ninja_model?(klass)
53
+ Builder::HasOne.build(self, name, options)
54
+ else
55
+ ActiveRecord::Associations::Builder::HasOne.build(self, name, options)
77
56
  end
78
57
  end
79
58
 
80
- def collection_accessor_methods(reflection, association_proxy_class, writer = true)
81
- collection_reader_method(reflection, association_proxy_class)
82
-
83
- if writer
84
- redefine_method("#{reflection.name}=") do |new_value|
85
- association = send(reflection.name)
86
- association.replace(new_value)
87
- association
88
- end
59
+ def belongs_to(name, options = {}, &extension)
60
+ klass = compute_klass(name, :belongs_to, options)
61
+ if NinjaModel.ninja_model?(klass)
62
+ Builder::BelongsTo.build(self, name, options)
63
+ else
64
+ ActiveRecord::Associations::Builder::BelongsTo.build(self, name, options)
89
65
  end
90
66
  end
91
67
 
92
- def collection_reader_method(reflection, association_proxy_class)
93
- redefine_method(reflection.name) do |*params|
94
- association = association_instance_get(reflection.name)
95
-
96
- if association.nil?
97
- association = association_proxy_class.new(self, reflection)
98
- end
99
- association_instance_set(reflection.name, association)
100
- association
68
+ def has_many(name, options = {}, &extension)
69
+ klass = compute_klass(name, :has_many, options)
70
+ if NinjaModel.ninja_model?(klass)
71
+ Builder::HasMany.build(self, name, options)
72
+ else
73
+ ActiveRecord::Associations::Builder::HasMany.build(self, name, options)
101
74
  end
102
75
  end
103
- end
104
- end
105
-
106
- module Associations
107
76
 
108
- autoload :AssociationProxy, 'ninja_model/associations/association_proxy'
109
- autoload :HasOneAssociation, 'ninja_model/associations/has_one_association'
110
- autoload :HasManyAssociation, 'ninja_model/associations/has_many_association'
111
- autoload :BelongsToAssociation, 'ninja_model/associations/belongs_to_association'
77
+ private
112
78
 
113
- def association_instance_get(name)
114
- ivar = "@#{name}"
115
- if instance_variable_defined?(ivar)
116
- association = instance_variable_get(ivar)
117
- association if association.respond_to?(:loaded?)
79
+ def compute_klass(name, macro, options)
80
+ klass = options[:class_name] || name
81
+ klass = klass.to_s.camelize
82
+ klass = klass.singularize if macro.eql?(:has_many)
83
+ klass = compute_type(klass)
118
84
  end
119
85
  end
120
-
121
- def association_instance_set(name, association)
122
- instance_variable_set("@#{name}", association)
123
- end
124
86
  end
125
87
  end
@@ -0,0 +1,146 @@
1
+ module NinjaModel
2
+ module Associations
3
+ class Association
4
+
5
+ attr_reader :owner, :target, :reflection
6
+
7
+ delegate :options, :to => :reflection
8
+
9
+ def initialize(owner, reflection)
10
+ @target = nil
11
+ @owner, @reflection = owner, reflection
12
+
13
+ reset
14
+ end
15
+
16
+ def reset
17
+ @loaded = false
18
+ @target = nil
19
+ end
20
+
21
+ def reload
22
+ reset
23
+ reset_scope
24
+ load_target
25
+ self unless target.nil?
26
+ end
27
+
28
+ def loaded?
29
+ @loaded
30
+ end
31
+
32
+ def loaded!
33
+ @loaded = true
34
+ end
35
+
36
+ def stale_target?
37
+ false
38
+ end
39
+
40
+ def target=(target)
41
+ @target = target
42
+ loaded!
43
+ end
44
+
45
+ def scoped
46
+ target_scope.merge(association_scope)
47
+ end
48
+
49
+ def target_scope
50
+ klass.scoped
51
+ end
52
+
53
+ def association_scope
54
+ if klass
55
+ @association_scope ||= AssociationScope.new(self).scope
56
+ end
57
+ end
58
+
59
+ def reset_scope
60
+ @association_scope = nil
61
+ end
62
+
63
+ def set_inverse_instance(record)
64
+ if record && invertible_for?(record)
65
+ inverse = record.association(inverse_reflection_for(record).name)
66
+ inverse.target = owner
67
+ end
68
+ end
69
+
70
+ def klass
71
+ reflection.klass
72
+ end
73
+
74
+ def target_scope
75
+ klass.scoped
76
+ end
77
+
78
+ def load_target
79
+ if find_target?
80
+ @target ||= find_target
81
+ end
82
+ loaded! unless loaded?
83
+ target
84
+ rescue NinjaModel::RecordNotFound
85
+ reset
86
+ end
87
+
88
+ private
89
+
90
+ def find_target?
91
+ !loaded? && (!owner.new_record? || foreign_key_present?) && klass
92
+ end
93
+
94
+ def build_record(attributes, options)
95
+ record = reflection.build_association(attributes, options) do |r|
96
+ attrs = create_scope.except(*r.changed)
97
+ r.assign_attributes(
98
+ create_scope.except(*r.changed)
99
+ )
100
+ end
101
+ end
102
+
103
+ def creation_attributes
104
+ attributes = {}
105
+ if options[:through]
106
+ raise NotImplementedError, "NinjaModel does not support through associations yet."
107
+ else
108
+ if reflection.macro.in?([:has_one, :has_many])
109
+ attributes[reflection.foreign_key] = owner[reflection.ninja_model_primary_key]
110
+ if reflection.options[:as]
111
+ attributes[reflection.type] = owner.class.base_class.name
112
+ end
113
+ end
114
+ attributes
115
+ end
116
+ end
117
+
118
+ def set_owner_attributes(record)
119
+ creation_attributes.each { |key, value| record[key] = value }
120
+ end
121
+
122
+ def foreign_key_present?
123
+ false
124
+ end
125
+
126
+ def raise_on_type_mismatch(record)
127
+ unless record.is_a?(reflection.klass) || record.is_a?(reflection.class_name.constantize)
128
+ message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})"
129
+ raise ActiveRecord::AssociationTypeMismatch, message
130
+ end
131
+ end
132
+
133
+ def inverse_reflection_for(record)
134
+ reflection.inverse_of
135
+ end
136
+
137
+ def invertible_for?(record)
138
+ inverse_reflection_for(record)
139
+ end
140
+
141
+ def association_class
142
+ @reflection.klass
143
+ end
144
+ end
145
+ end
146
+ end