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.
- data/.gitignore +1 -1
- data/Gemfile +1 -0
- data/Rakefile +6 -0
- data/lib/ninja_model.rb +23 -7
- data/lib/ninja_model/adapters.rb +25 -20
- data/lib/ninja_model/adapters/adapter_manager.rb +2 -0
- data/lib/ninja_model/adapters/adapter_pool.rb +2 -0
- data/lib/ninja_model/associations.rb +63 -101
- data/lib/ninja_model/associations/association.rb +146 -0
- data/lib/ninja_model/associations/association_scope.rb +39 -0
- data/lib/ninja_model/associations/belongs_to_association.rb +33 -21
- data/lib/ninja_model/associations/builder/association.rb +57 -0
- data/lib/ninja_model/associations/builder/belongs_to.rb +33 -0
- data/lib/ninja_model/associations/builder/collection_association.rb +60 -0
- data/lib/ninja_model/associations/builder/has_many.rb +11 -0
- data/lib/ninja_model/associations/builder/has_one.rb +20 -0
- data/lib/ninja_model/associations/builder/singular_association.rb +49 -0
- data/lib/ninja_model/associations/collection_association.rb +103 -0
- data/lib/ninja_model/associations/collection_proxy.rb +45 -0
- data/lib/ninja_model/associations/has_many_association.rb +19 -43
- data/lib/ninja_model/associations/has_one_association.rb +52 -6
- data/lib/ninja_model/associations/singular_association.rb +61 -0
- data/lib/ninja_model/attribute.rb +5 -2
- data/lib/ninja_model/attribute_methods.rb +35 -40
- data/lib/ninja_model/base.rb +31 -39
- data/lib/ninja_model/identity.rb +8 -6
- data/lib/ninja_model/rails_ext/active_record.rb +69 -225
- data/lib/ninja_model/railtie.rb +0 -9
- data/lib/ninja_model/reflection.rb +103 -20
- data/lib/ninja_model/relation.rb +2 -2
- data/lib/ninja_model/relation/query_methods.rb +16 -0
- data/lib/ninja_model/version.rb +1 -1
- data/ninja-model.gemspec +2 -1
- data/spec/db/schema.rb +15 -0
- data/spec/{ninja_model → lib/ninja_model}/adapters/abstract_adapter_spec.rb +0 -0
- data/spec/lib/ninja_model/adapters/adapter_manager_spec.rb +72 -0
- data/spec/lib/ninja_model/adapters/adapter_pool_spec.rb +230 -0
- data/spec/lib/ninja_model/adapters_spec.rb +120 -0
- data/spec/lib/ninja_model/associations/belongs_to_association_spec.rb +76 -0
- data/spec/lib/ninja_model/associations/has_many_association_spec.rb +118 -0
- data/spec/lib/ninja_model/associations/has_one_association_spec.rb +80 -0
- data/spec/{ninja_model → lib/ninja_model}/attribute_methods_spec.rb +2 -2
- data/spec/{ninja_model → lib/ninja_model}/attribute_spec.rb +4 -0
- data/spec/{ninja_model → lib/ninja_model}/base_spec.rb +0 -0
- data/spec/{ninja_model → lib/ninja_model}/identity_spec.rb +0 -0
- data/spec/{ninja_model → lib/ninja_model}/persistence_spec.rb +0 -0
- data/spec/{ninja_model → lib/ninja_model}/predicate_spec.rb +0 -0
- data/spec/{ninja_model → lib/ninja_model}/query_methods_spec.rb +0 -0
- data/spec/{ninja_model → lib/ninja_model}/reflection_spec.rb +7 -9
- data/spec/{ninja_model → lib/ninja_model}/relation_spec.rb +0 -0
- data/spec/{ninja_model → lib/ninja_model}/symbol_spec.rb +0 -0
- data/spec/{ninja_model → lib/ninja_model}/validation_spec.rb +0 -0
- data/spec/{ninja_model_spec.rb → lib/ninja_model_spec.rb} +0 -0
- data/spec/models/bio.rb +8 -0
- data/spec/models/body.rb +7 -0
- data/spec/models/category.rb +7 -0
- data/spec/models/email_address.rb +3 -0
- data/spec/models/post.rb +13 -0
- data/spec/models/tag.rb +3 -0
- data/spec/models/user.rb +4 -0
- data/spec/spec_helper.rb +38 -5
- data/spec/support/dummy_adapter/adapter.rb +48 -0
- data/spec/support/factories/bio.rb +11 -0
- data/spec/support/factories/body.rb +12 -0
- data/spec/support/factories/email_address.rb +5 -0
- data/spec/support/factories/post.rb +12 -0
- data/spec/support/factories/tag.rb +5 -0
- data/spec/support/factories/user.rb +5 -0
- metadata +121 -63
- data/spec/ninja_model/adapters/adapter_manager_spec.rb +0 -69
- data/spec/ninja_model/adapters/adapter_pool_spec.rb +0 -230
- 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
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
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 <
|
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
|
-
|
6
|
-
|
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
|
12
|
-
|
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
|
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
|
-
|
134
|
-
|
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
|
-
|
8
|
-
|
9
|
-
|
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
|
-
|
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
|
21
|
-
|
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::
|
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
|
-
|
118
|
-
|
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
|
|
data/lib/ninja_model/base.rb
CHANGED
@@ -1,14 +1,4 @@
|
|
1
|
-
require 'ninja_model/
|
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.
|
49
|
+
Thread.current[key] = Thread.current[key].presence || self.default_scopes.dup
|
70
50
|
end
|
71
51
|
|
72
|
-
def default_scope(
|
73
|
-
|
74
|
-
self.
|
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
|
-
|
126
|
-
|
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
|
-
|
145
|
-
|
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)
|