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
@@ -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)