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
@@ -0,0 +1,45 @@
1
+ module NinjaModel
2
+ module Associations
3
+ class CollectionProxy
4
+
5
+ delegate :order, :limit, :where, :to => :scoped
6
+
7
+ delegate :target, :load_target, :loaded?, :scoped, :to => :@association
8
+
9
+ delegate :select, :find, :first, :last, :build, :create, :create,
10
+ :count, :size, :length, :empty?, :any?, :to => :@association
11
+
12
+ def initialize(association)
13
+ @association = association
14
+ end
15
+
16
+ def proxy_association
17
+ @association
18
+ end
19
+
20
+ def respond_to?(name, include_private = false)
21
+ super ||
22
+ (load_target && target.respond_to?(name, include_private)) ||
23
+ proxy_association.klass.respond_to?(name, include_private)
24
+ end
25
+
26
+ def method_missing(method, *args, &block)
27
+ if target.respond_to?(method) || (!proxy_association.klass.respond_to?(method) && Class.respond_to?(method))
28
+ if load_target
29
+ if target.respond_to?(method)
30
+ target.send(method, *args, &block)
31
+ else
32
+ begin
33
+ super
34
+ rescue NoMethodError => e
35
+ raise e, e.message.sub(/ for #<.*$/, "via proxy for #{target}")
36
+ end
37
+ end
38
+ end
39
+ else
40
+ scoped.send(method, *args, &block)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -1,48 +1,24 @@
1
1
  module NinjaModel
2
2
  module Associations
3
- class HasManyAssociation
4
- def initialize(owner, reflection)
5
- @owner, @reflection = owner, reflection
6
- @relation = apply_default_scope(reflection.klass.scoped)
7
- end
8
-
9
- delegate :each, :collect, :map, :to_a, :size, :blank?, :empty?, :to => :relation
10
-
11
- def method_missing(method, *args)
12
- if @relation.respond_to?(method)
13
- @relation.send(method, *args)
14
- elsif @relation.klass.respond_to?(method)
15
- apply_default_scope(@relation.klass.scoped).send(method, *args)
16
- else
17
- super
18
- end
19
- end
20
-
21
- def relation
22
- @relation
23
- end
24
-
25
- def inspect
26
- @relation.to_a.inspect
27
- end
28
-
29
- def replace(other_array)
30
- @current = other_array
31
- end
32
-
33
- def blank?
34
- @relation.blank?
35
- end
36
-
37
- def to_ary
38
- @relation.to_a
39
- end
40
-
41
- private
42
-
43
- def apply_default_scope(scoping)
44
- scoping.where(@reflection.primary_key_name.to_sym.eq(@owner.id))
45
- end
3
+ class HasManyAssociation < CollectionAssociation
4
+
5
+ #def insert_record(record, validate = true, raise = false)
6
+ # set_owner_attributes(record)
7
+
8
+ # if raise
9
+ # record.save!(:validate => validate)
10
+ # else
11
+ # record.save(:validate => validate)
12
+ # end
13
+ #end
14
+
15
+ #def association_scope
16
+ # scope = super
17
+ # puts "primary_key: #{reflection.primary_key}"
18
+ # puts "foreign_key: #{reflection.foreign_key}"
19
+ # scope = scope.where(reflection.foreign_key => owner.send(reflection.primary_key))
20
+ # scope
21
+ #end
46
22
  end
47
23
  end
48
24
  end
@@ -1,18 +1,64 @@
1
1
  module NinjaModel
2
2
  module Associations
3
- class HasOneAssociation < AssociationProxy
3
+ class HasOneAssociation < SingularAssociation
4
+
5
+ def replace(record, save = true)
6
+ raise_on_type_mismatch(record) if record
7
+ load_target
8
+
9
+ if target && target != record
10
+ remove_target!(options[:dependent]) unless target.destroyed?
11
+ end
12
+
13
+ if record
14
+ set_owner_attributes(record)
15
+ set_inverse_instance(record)
16
+
17
+ if owner.persisted? && save && !record.save
18
+ nullify_owner_attributes(record)
19
+ set_owner_attributes(target) if target
20
+ raise RecordNotSaved, "Failed to save the new associated #{reflection.name}."
21
+ end
22
+ end
23
+
24
+ self.target = record
4
25
 
5
- def create(attrs = {}, replace_existing = true)
6
- new_record
26
+ end
27
+
28
+ def delete(method = options[:dependent])
29
+ if load_target
30
+ case method
31
+ when :delete
32
+ target.delete
33
+ when :destroy
34
+ target.destroy
35
+ when :nullify
36
+ target.update_attribute(reflection.foreign_key, nil)
37
+ end
38
+ end
7
39
  end
8
40
 
9
41
  private
10
42
 
11
- def find_target
12
- @reflection.klass.scoped.where(@reflection.primary_key_name => @owner.send(:id)).first
43
+ def set_new_record(record)
44
+ replace(record, false)
45
+ end
46
+
47
+ def remove_target!(method)
48
+ if method.in?([:delete, :destroy])
49
+ target.send(method)
50
+ else
51
+ nullify_owner_attributes(target)
52
+
53
+ if target.persisted? && owner.persisted? && !target.save
54
+ set_owner_attributes(target)
55
+ raise RecordNotSaved, "Failed to remove the existing associated #{reflection.name}. The record failed to save when after its foreign key was set to nil."
56
+ end
57
+ end
13
58
  end
14
59
 
15
- def new_record(replace_existing)
60
+ def nullify_owner_attributes(record)
61
+ record[reflection.foreign_key] = nil
16
62
  end
17
63
  end
18
64
  end
@@ -0,0 +1,61 @@
1
+ module NinjaModel
2
+ module Associations
3
+ class SingularAssociation < Association
4
+ def reader(force_reload = false)
5
+ if force_reload
6
+ klass.uncached { reload }
7
+ elsif !loaded? || stale_target?
8
+ reload
9
+ end
10
+
11
+ target
12
+ end
13
+
14
+ def writer(record)
15
+ replace(record)
16
+ end
17
+
18
+ def create(attributes = {}, options = {}, &block)
19
+ create_record(attributes, options, &block)
20
+ end
21
+
22
+ def create!(attributes = {}, options = {}, &block)
23
+ create_record(attributes, options, true, &block)
24
+ end
25
+
26
+ def build(attributes = {}, options = {})
27
+ record = build_record(attributes, options)
28
+ yield(record) if block_given?
29
+ set_new_record(record)
30
+ record
31
+ end
32
+
33
+ private
34
+
35
+ def create_scope
36
+ scoped.scope_for_create.stringify_keys.except(klass.primary_key)
37
+ end
38
+
39
+ def find_target
40
+ scoped.first.tap { |record| set_inverse_instance(record) }
41
+ end
42
+
43
+ def replace(record)
44
+ raise NotImplementedError, "Subclasses must implement a replace(record) method"
45
+ end
46
+
47
+ def set_new_record(record)
48
+ replace(record)
49
+ end
50
+
51
+ def create_record(attributes, options, raise_error = false)
52
+ record = build_record(attributes, options)
53
+ yield(record) if block_given?
54
+ saved = record.save
55
+ set_new_record(record)
56
+ raise RecordInvalid.new(record) if !saved && raise_error
57
+ record
58
+ end
59
+ end
60
+ end
61
+ end
@@ -130,8 +130,11 @@ module NinjaModel
130
130
  end
131
131
 
132
132
  def fallback_string_to_date(string)
133
- ::Date.strptime(string, I18n.translate('date.formats.default'))
134
- #new_date(*::Date._parse(string, false).values_at(:year, :mon, :mday))
133
+ begin
134
+ ::Date.strptime(string, I18n.translate('date.formats.default'))
135
+ rescue ArgumentError
136
+ nil
137
+ end
135
138
  end
136
139
 
137
140
  def fallback_string_to_time(string)
@@ -1,15 +1,35 @@
1
1
  module NinjaModel
2
- class Base
3
- class UnknownAttributeError < NinjaModelError; end
4
- include ActiveModel::AttributeMethods
5
- include ActiveModel::Dirty
6
2
 
7
- #class_attribute :model_attributes
8
- #self.model_attributes = []
9
- attribute_method_suffix('', '=', '_before_type_cast')
3
+ class UnknownAttributeError < NinjaModelError; end
4
+ class AttributeAssignmentError < NinjaModelError
5
+ attr_reader :exception, :attribute
6
+ attr_reader :mymessage
7
+ def initialize(message, exception, attribute)
8
+ @exception = exception
9
+ @attribute = attribute
10
+ @message = message
11
+ @mymessage = message
12
+ end
13
+ end
14
+ class MultiparameterAssignmentErrors < NinjaModelError
15
+ attr_reader :errors
16
+ def initialize(errors)
17
+ @errors = errors
18
+ end
19
+ end
10
20
 
11
- class << self
21
+ module AttributeMethods
22
+ extend ActiveSupport::Concern
12
23
 
24
+ included do
25
+ include ActiveModel::AttributeMethods
26
+ include ActiveModel::Dirty
27
+ class_attribute :model_attributes
28
+ self.model_attributes = []
29
+ attribute_method_suffix('', '=', '_before_type_cast')
30
+ end
31
+
32
+ module ClassMethods
13
33
  def attribute(name, data_type, *args)
14
34
  name = name.to_s
15
35
  opts = args.extract_options!
@@ -17,14 +37,8 @@ module NinjaModel
17
37
  self.primary_key = name if primary.eql?(true)
18
38
  default = args.first unless args.blank?
19
39
  new_attr = Attribute.new(name, data_type, opts)
20
- self.model_attributes << new_attr
21
- define_attribute_methods(true)
22
- end
23
-
24
- def define_attribute_methods(force = false)
25
- return unless self.model_attributes
26
- undefine_attribute_methods if force
27
- super(self.model_attributes.map { |attr| attr.name })
40
+ self.model_attributes = model_attributes + [new_attr]
41
+ define_attribute_method name
28
42
  end
29
43
 
30
44
  def columns
@@ -43,25 +57,6 @@ module NinjaModel
43
57
 
44
58
  alias :column_names :attribute_names
45
59
  end
46
- end
47
-
48
- module AttributeMethods
49
- class AttributeAssignmentError < NinjaModelError
50
- attr_reader :exception, :attribute
51
- attr_reader :mymessage
52
- def initialize(message, exception, attribute)
53
- @exception = exception
54
- @attribute = attribute
55
- @message = message
56
- @mymessage = message
57
- end
58
- end
59
- class MultiparameterAssignmentErrors < NinjaModelError
60
- attr_reader :errors
61
- def initialize(errors)
62
- @errors = errors
63
- end
64
- end
65
60
 
66
61
  def attributes_from_model_attributes
67
62
  self.class.model_attributes.inject({}) do |result, attr|
@@ -106,17 +101,17 @@ module NinjaModel
106
101
  if k.include?('(')
107
102
  multi_parameter_attributes << [k, v]
108
103
  else
109
- respond_to?("#{k}=".to_sym) ? send("#{k}=".to_sym, v) : raise(NinjaModel::Base::UnknownAttributeError, "unknown attribute: #{k}")
104
+ respond_to?("#{k}=".to_sym) ? send("#{k}=".to_sym, v) : raise(NinjaModel::UnknownAttributeError, "unknown attribute: #{k}")
110
105
  end
111
106
  end
112
107
 
113
108
  assign_multiparameter_attributes(multi_parameter_attributes)
114
109
  end
115
110
 
116
- def attribute_method?(name)
117
- name = name.to_s
118
- self.class.model_attributes_hash.key?(name)
119
- end
111
+ #def attribute_method?(name)
112
+ # name = name.to_s
113
+ # self.class.model_attributes_hash.key?(name)
114
+ #end
120
115
 
121
116
  private
122
117
 
@@ -1,14 +1,4 @@
1
- require 'ninja_model/attribute_methods'
2
- require 'ninja_model/associations'
3
- require 'ninja_model/adapters'
4
- require 'ninja_model/callbacks'
5
- require 'ninja_model/identity'
6
- require 'ninja_model/persistence'
7
- require 'ninja_model/predicate'
8
- require 'ninja_model/reflection'
9
- require 'ninja_model/relation'
10
- require 'ninja_model/validation'
11
- require 'ninja_model/attribute'
1
+ require 'ninja_model/core_ext/symbol'
12
2
  require 'active_record/named_scope'
13
3
  require 'active_record/aggregations'
14
4
 
@@ -25,29 +15,19 @@ module NinjaModel
25
15
  extend ActiveModel::Translation
26
16
  extend ActiveModel::Naming
27
17
  include ActiveModel::Observing
28
- include ActiveModel::Dirty
29
18
  include ActiveRecord::Aggregations
30
19
  include ActiveRecord::NamedScope
31
20
  include ActiveModel::Serializers::JSON
32
21
  include ActiveModel::Serializers::Xml
33
22
 
34
23
  define_model_callbacks :initialize, :find, :touch, :only => :after
24
+ class_attribute :pluralize_table_names, :instance_writer => false
25
+ self.pluralize_table_names = true
26
+
27
+ class_attribute :default_scopes
28
+ self.default_scopes = []
35
29
 
36
30
  class << self
37
- def inherited(subclass)
38
- subclass.class_attribute :model_attributes
39
- if self.respond_to?(:model_attributes)
40
- subclass.model_attributes = self.model_attributes.dup
41
- else
42
- subclass.model_attributes = []
43
- end
44
- subclass.class_attribute :default_scoping
45
- if self.respond_to?(:default_scoping)
46
- subclass.default_scoping = self.default_scoping.dup
47
- else
48
- subclass.default_scoping = []
49
- end
50
- end
51
31
 
52
32
  delegate :find, :first, :last, :all, :exists?, :to => :scoped
53
33
  delegate :where, :order, :limit, :to => :scoped
@@ -66,12 +46,12 @@ module NinjaModel
66
46
 
67
47
  def scoped_methods
68
48
  key = "#{self}_scoped_methods".to_sym
69
- Thread.current[key] = Thread.current[key].presence || self.default_scoping.dup
49
+ Thread.current[key] = Thread.current[key].presence || self.default_scopes.dup
70
50
  end
71
51
 
72
- def default_scope(options = {})
73
- reset_scoped_methods
74
- self.default_scoping << build_finder_relation(options, default_scoping.pop)
52
+ def default_scope(scope = {})
53
+ scope = Proc.new if block_given?
54
+ self.default_scopes = default_scopes + [scope]
75
55
  end
76
56
 
77
57
  def current_scope
@@ -87,7 +67,6 @@ module NinjaModel
87
67
  Thread.current["#{self}_scoped_methods".to_sym] = nil
88
68
  end
89
69
 
90
- private
91
70
 
92
71
  def build_finder_relation(options = {}, scope = nil)
93
72
  relation = options.is_a?(Hash) ? unscoped.apply_finder_options(options) : options
@@ -121,15 +100,27 @@ module NinjaModel
121
100
  end
122
101
  end
123
102
 
103
+ def assign_attributes(new_attributes, options = {})
104
+ return unless new_attributes
105
+
106
+ attributes = new_attributes.stringify_keys
107
+
108
+ attributes.each do |k, v|
109
+ if respond_to?("#{k}=")
110
+ send("#{k}=", v)
111
+ else
112
+ raise(StandardError, "unknown attribute: #{k}")
113
+ end
114
+ end
115
+ end
116
+
124
117
  def attributes
125
- attrs = {}
126
- self.class.attribute_names.each { |name|
127
- attrs[name] = read_attribute(name)
118
+ self.class.attribute_names.inject({}) { |h, v|
119
+ h[v] = read_attribute(v); h
128
120
  }
129
- attrs
130
121
  end
131
122
 
132
- def initialize(attributes = nil)
123
+ def initialize(attributes = nil, options = {})
133
124
  @attributes = attributes_from_model_attributes
134
125
  @association_cache = {}
135
126
  @aggregation_cache = {}
@@ -141,9 +132,8 @@ module NinjaModel
141
132
 
142
133
  self.attributes = attributes unless attributes.nil?
143
134
 
144
- result = yield self if block_given?
145
- _run_initialize_callbacks
146
- result
135
+ yield self if block_given?
136
+ run_callbacks :initialize
147
137
  end
148
138
 
149
139
  def instantiate(record)
@@ -175,3 +165,5 @@ module NinjaModel
175
165
  end
176
166
  end
177
167
  end
168
+
169
+ ActiveSupport.run_load_hooks(:ninja_model, NinjaModel::Base)