ninja-model 0.4.2 → 0.5.1
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.
- data/.gitignore +19 -0
- data/Rakefile +0 -7
- data/autotest/discover.rb +1 -0
- data/lib/ninja_model.rb +22 -26
- data/lib/ninja_model/adapters.rb +33 -43
- data/lib/ninja_model/adapters/abstract_adapter.rb +2 -10
- data/lib/ninja_model/adapters/adapter_manager.rb +17 -10
- data/lib/ninja_model/adapters/adapter_pool.rb +15 -17
- data/lib/ninja_model/adapters/adapter_specification.rb +3 -3
- data/lib/ninja_model/associations.rb +25 -106
- data/lib/ninja_model/associations/association_proxy.rb +119 -1
- data/lib/ninja_model/associations/belongs_to_association.rb +5 -1
- data/lib/ninja_model/attribute.rb +130 -0
- data/lib/ninja_model/{attributes.rb → attribute_methods.rb} +21 -42
- data/lib/ninja_model/base.rb +23 -20
- data/lib/ninja_model/callbacks.rb +2 -11
- data/lib/ninja_model/identity.rb +6 -11
- data/lib/ninja_model/persistence.rb +15 -30
- data/lib/ninja_model/predicate.rb +4 -4
- data/lib/ninja_model/rails_ext/active_record.rb +187 -0
- data/lib/ninja_model/railtie.rb +14 -8
- data/lib/ninja_model/reflection.rb +7 -14
- data/lib/ninja_model/relation.rb +5 -3
- data/lib/ninja_model/relation/finder_methods.rb +4 -8
- data/lib/ninja_model/relation/spawn_methods.rb +1 -1
- data/lib/ninja_model/validation.rb +6 -23
- data/lib/ninja_model/version.rb +1 -1
- data/ninja-model.gemspec +28 -0
- data/spec/ninja_model/adapters/abstract_adapter_spec.rb +45 -0
- data/spec/ninja_model/adapters/adapter_manager_spec.rb +69 -0
- data/spec/ninja_model/adapters/adapter_pool_spec.rb +210 -48
- data/spec/ninja_model/adapters_spec.rb +77 -0
- data/spec/ninja_model/attribute_methods_spec.rb +95 -0
- data/spec/ninja_model/attribute_spec.rb +129 -0
- data/spec/ninja_model/base_spec.rb +4 -52
- data/spec/ninja_model/identity_spec.rb +16 -32
- data/spec/ninja_model/persistence_spec.rb +130 -4
- data/spec/ninja_model/predicate_spec.rb +40 -6
- data/spec/ninja_model/query_methods_spec.rb +76 -74
- data/spec/ninja_model/reflection_spec.rb +63 -0
- data/spec/ninja_model/relation_spec.rb +213 -20
- data/spec/ninja_model/symbol_spec.rb +19 -0
- data/spec/ninja_model/validation_spec.rb +18 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/support/matchers/convert.rb +30 -0
- metadata +85 -63
- data/lib/ninja_model/associations/active_record_proxy.rb +0 -53
- data/lib/ninja_model/associations/ninja_model_proxy.rb +0 -46
- data/lib/ninja_model/configuration.rb +0 -20
- data/lib/ninja_model/errors.rb +0 -5
- data/lib/ninja_model/log_subscriber.rb +0 -18
- data/lib/ninja_model/scoping.rb +0 -50
- data/spec/ninja_model/attributes_spec.rb +0 -85
- data/spec/ninja_model/scoping_spec.rb +0 -40
@@ -2,7 +2,125 @@ require 'active_record/associations/association_proxy'
|
|
2
2
|
|
3
3
|
module NinjaModel
|
4
4
|
module Associations
|
5
|
-
class AssociationProxy
|
5
|
+
class AssociationProxy
|
6
|
+
alias_method :proxy_respond_to?, :respond_to?
|
7
|
+
alias_method :proxy_extend, :extend
|
8
|
+
delegate :to_param, :to => :proxy_target
|
9
|
+
instance_methods.each { |m| undef_method m unless m.to_s =~ /^(?:nil\?|send|object_id|to_a)$|^__|^respond_to_missing|proxy_/ }
|
10
|
+
|
11
|
+
def initialize(owner, reflection)
|
12
|
+
@owner, @reflection = owner, reflection
|
13
|
+
@updated = false
|
14
|
+
Array.wrap(reflection.options[:extend]).each { |ext| proxy_extend(ext) }
|
15
|
+
reset
|
16
|
+
end
|
17
|
+
|
18
|
+
def proxy_owner
|
19
|
+
@owner
|
20
|
+
end
|
21
|
+
|
22
|
+
def proxy_reflection
|
23
|
+
@reflection
|
24
|
+
end
|
25
|
+
|
26
|
+
def proxy_target
|
27
|
+
@target
|
28
|
+
end
|
29
|
+
|
30
|
+
def respond_to?(*args)
|
31
|
+
proxy_respond_to?(*args) || (load_target && @target.respond_to?(*args))
|
32
|
+
end
|
33
|
+
|
34
|
+
def ===(other)
|
35
|
+
load_target
|
36
|
+
other === @target
|
37
|
+
end
|
38
|
+
|
39
|
+
def reload
|
40
|
+
reset
|
41
|
+
load_target
|
42
|
+
self unless @target.nil?
|
43
|
+
end
|
44
|
+
|
45
|
+
def reset
|
46
|
+
@loaded = false
|
47
|
+
@target = nil
|
48
|
+
end
|
49
|
+
|
50
|
+
def loaded?
|
51
|
+
@loaded
|
52
|
+
end
|
53
|
+
|
54
|
+
def loaded
|
55
|
+
@loaded = true
|
56
|
+
end
|
57
|
+
|
58
|
+
def target
|
59
|
+
@target
|
60
|
+
end
|
61
|
+
|
62
|
+
def target=(target)
|
63
|
+
@target = target
|
64
|
+
loaded
|
65
|
+
end
|
66
|
+
|
67
|
+
def inspect
|
68
|
+
load_target
|
69
|
+
@target.inspect
|
70
|
+
end
|
71
|
+
|
72
|
+
def send(method, *args)
|
73
|
+
if proxy_respond_to?(method)
|
74
|
+
super
|
75
|
+
else
|
76
|
+
load_target
|
77
|
+
@target.send(method, *args)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
protected
|
82
|
+
|
83
|
+
def dependent?
|
84
|
+
@reflection.options[:dependent]
|
85
|
+
end
|
86
|
+
|
87
|
+
def with_scope(*args, &block)
|
88
|
+
@reflection.klass.send :with_scope, *args, &block
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def method_missing(method, *args)
|
94
|
+
if load_target
|
95
|
+
unless @target.respond_to?(method)
|
96
|
+
message = "undefined method '#{method.to_s}' for \"#{@target}\":#{@target.class.to_s}"
|
97
|
+
raise NoMethodError, message
|
98
|
+
end
|
99
|
+
|
100
|
+
if block_given?
|
101
|
+
@target.send(method, *args) { |*block_args| yield(*block_args) }
|
102
|
+
else
|
103
|
+
@target.send(method, *args)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def load_target
|
109
|
+
return nil unless defined?(@loaded)
|
110
|
+
|
111
|
+
if !loaded? and (@owner.persisted? || foreign_key_present)
|
112
|
+
@target = find_target
|
113
|
+
end
|
114
|
+
|
115
|
+
@loaded = true
|
116
|
+
@target
|
117
|
+
rescue NinjaModel::RecordNotFound
|
118
|
+
reset
|
119
|
+
end
|
120
|
+
|
121
|
+
def foreign_key_present
|
122
|
+
false
|
123
|
+
end
|
6
124
|
end
|
7
125
|
end
|
8
126
|
end
|
@@ -20,7 +20,11 @@ module NinjaModel
|
|
20
20
|
private
|
21
21
|
|
22
22
|
def find_target
|
23
|
-
|
23
|
+
if @reflection.options[:primary_key]
|
24
|
+
@reflection.klass.scoped.where( @reflection.options[:primary_key] => @owner.send(@reflection.primary_key_name)).first
|
25
|
+
else
|
26
|
+
@reflection.klass.scoped.where( :id => @owner.send(@reflection.primary_key_name)).first
|
27
|
+
end
|
24
28
|
end
|
25
29
|
|
26
30
|
def record_id(record)
|
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'active_support/core_ext/date_time/conversions'
|
2
|
+
|
3
|
+
module NinjaModel
|
4
|
+
class UnsupportedType < NinjaModelError; end
|
5
|
+
class InvalidConversion < NinjaModelError; end
|
6
|
+
|
7
|
+
class Attribute
|
8
|
+
attr_reader :name, :type, :default
|
9
|
+
|
10
|
+
VALID_TYPES = [:string, :integer, :float, :date, :datetime, :boolean]
|
11
|
+
|
12
|
+
def initialize(name, type, options = {})
|
13
|
+
@name, @type = name.to_s, type
|
14
|
+
@default = options[:default]
|
15
|
+
raise UnsupportedType.new("Invalid type: #{@type}") unless VALID_TYPES.include?(@type)
|
16
|
+
end
|
17
|
+
|
18
|
+
def number?
|
19
|
+
[:integer, :float].include?(@type)
|
20
|
+
end
|
21
|
+
|
22
|
+
#
|
23
|
+
# Most of the following code was taken from ActiveRecord. Credit to the
|
24
|
+
# Rails team is due.
|
25
|
+
#
|
26
|
+
|
27
|
+
def convert(value)
|
28
|
+
case type
|
29
|
+
when :string then self.class.convert_to_string(value)
|
30
|
+
when :integer then value.to_i rescue value ? 1 : 0
|
31
|
+
when :float then value.to_f rescue value ? 1.0 : 0.0
|
32
|
+
when :date then self.class.string_to_date(value)
|
33
|
+
when :datetime then self.class.string_to_time(value)
|
34
|
+
when :boolean then self.class.value_to_boolean(value)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE'].to_set
|
39
|
+
FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE'].to_set
|
40
|
+
|
41
|
+
module Format
|
42
|
+
ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/
|
43
|
+
ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
|
44
|
+
end
|
45
|
+
|
46
|
+
class << self
|
47
|
+
|
48
|
+
def convert_to_string(value)
|
49
|
+
case value
|
50
|
+
when String, NilClass
|
51
|
+
value
|
52
|
+
when Fixnum, Float, Date, DateTime, TrueClass, FalseClass
|
53
|
+
value.to_s
|
54
|
+
else
|
55
|
+
raise InvalidConversion.new("Unable to convert #{value.inspect} to string")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def string_to_date(string)
|
60
|
+
return string unless string.is_a?(String)
|
61
|
+
return nil if string.empty?
|
62
|
+
|
63
|
+
fast_string_to_date(string) || fallback_string_to_date(string)
|
64
|
+
end
|
65
|
+
|
66
|
+
def string_to_time(string)
|
67
|
+
return string unless string.is_a?(String)
|
68
|
+
return nil if string.empty?
|
69
|
+
|
70
|
+
fast_string_to_time(string) || fallback_string_to_time(string)
|
71
|
+
end
|
72
|
+
|
73
|
+
# convert something to a boolean
|
74
|
+
def value_to_boolean(value)
|
75
|
+
if value.is_a?(String) && value.blank?
|
76
|
+
nil
|
77
|
+
else
|
78
|
+
TRUE_VALUES.include?(value)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
protected
|
83
|
+
|
84
|
+
# '0.123456' -> 123456
|
85
|
+
# '1.123456' -> 123456
|
86
|
+
def microseconds(time)
|
87
|
+
((time[:sec_fraction].to_f % 1) * 1_000_000).to_i
|
88
|
+
end
|
89
|
+
|
90
|
+
def new_date(year, mon, mday)
|
91
|
+
if year && year != 0
|
92
|
+
Date.new(year, mon, mday) rescue nil
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def new_time(year, mon, mday, hour, min, sec, microsec)
|
97
|
+
# Treat 0000-00-00 00:00:00 as nil.
|
98
|
+
return nil if year.nil? || year == 0
|
99
|
+
|
100
|
+
DateTime.new(year, mon, mday, hour, min, sec, microsec) rescue nil
|
101
|
+
end
|
102
|
+
|
103
|
+
def fast_string_to_date(string)
|
104
|
+
if string =~ Format::ISO_DATE
|
105
|
+
new_date $1.to_i, $2.to_i, $3.to_i
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Doesn't handle time zones.
|
110
|
+
def fast_string_to_time(string)
|
111
|
+
if string =~ Format::ISO_DATETIME
|
112
|
+
microsec = ($7.to_f * 1_000_000).to_i
|
113
|
+
res = new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec
|
114
|
+
res
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def fallback_string_to_date(string)
|
119
|
+
new_date(*::Date._parse(string, false).values_at(:year, :mon, :mday))
|
120
|
+
end
|
121
|
+
|
122
|
+
def fallback_string_to_time(string)
|
123
|
+
time_hash = Date._parse(string)
|
124
|
+
time_hash[:sec_fraction] = microseconds(time_hash)
|
125
|
+
|
126
|
+
new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction))
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -1,48 +1,23 @@
|
|
1
|
-
require 'active_support'
|
2
|
-
require 'active_model/attribute_methods'
|
3
|
-
|
4
1
|
module NinjaModel
|
5
|
-
class
|
6
|
-
|
7
|
-
|
8
|
-
alias :primary :primary_key
|
9
|
-
|
10
|
-
def initialize(name, type, default, owner_class, options)
|
11
|
-
@name, @type, @default = name.to_s, type, default
|
12
|
-
@owner_class = owner_class
|
13
|
-
@options = options
|
14
|
-
@primary_key = options.key?(:primary_key) && options[:primary_key]
|
15
|
-
end
|
16
|
-
|
17
|
-
def define_methods!
|
18
|
-
@owner_class.define_attribute_methods(true)
|
19
|
-
@owner_class.primary_key = name.to_sym if @primary_key
|
20
|
-
end
|
2
|
+
class Base
|
3
|
+
include ActiveModel::AttributeMethods
|
4
|
+
include ActiveModel::Dirty
|
21
5
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
when :text then value
|
26
|
-
when :integer then value.to_i rescue value ? 1 : 0
|
27
|
-
when :float then value.to_f
|
28
|
-
when :date then ActiveRecord::ConnectionAdapters::Column.string_to_date(value)
|
29
|
-
else value
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
6
|
+
class_inheritable_accessor :model_attributes
|
7
|
+
self.model_attributes = []
|
8
|
+
attribute_method_suffix('', '=', '_before_type_cast')
|
33
9
|
|
34
|
-
|
35
|
-
extend ActiveSupport::Concern
|
36
|
-
include ActiveModel::AttributeMethods
|
10
|
+
class << self
|
37
11
|
|
38
|
-
module ClassMethods
|
39
12
|
def attribute(name, data_type, *args)
|
40
13
|
name = name.to_s
|
41
14
|
opts = args.extract_options!
|
15
|
+
primary = opts.delete(:primary_key)
|
16
|
+
self.primary_key = name if primary.eql?(true)
|
42
17
|
default = args.first unless args.blank?
|
43
|
-
new_attr = Attribute.new(name, data_type,
|
18
|
+
new_attr = Attribute.new(name, data_type, opts)
|
44
19
|
self.model_attributes << new_attr
|
45
|
-
|
20
|
+
define_attribute_methods(true)
|
46
21
|
end
|
47
22
|
|
48
23
|
def define_attribute_methods(force = false)
|
@@ -67,12 +42,9 @@ module NinjaModel
|
|
67
42
|
|
68
43
|
alias :column_names :attribute_names
|
69
44
|
end
|
45
|
+
end
|
70
46
|
|
71
|
-
|
72
|
-
class_inheritable_accessor :model_attributes
|
73
|
-
self.model_attributes = []
|
74
|
-
attribute_method_suffix('', '=')
|
75
|
-
end
|
47
|
+
module AttributeMethods
|
76
48
|
|
77
49
|
def attributes_from_model_attributes
|
78
50
|
self.class.model_attributes.inject({}) do |result, attr|
|
@@ -128,7 +100,14 @@ module NinjaModel
|
|
128
100
|
end
|
129
101
|
|
130
102
|
def attribute=(name, value)
|
131
|
-
|
103
|
+
unless read_attribute(name).eql?(value)
|
104
|
+
attribute_will_change!(name)
|
105
|
+
write_attribute(name, value)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def attribute_before_type_cast(name)
|
110
|
+
@attributes[name]
|
132
111
|
end
|
133
112
|
end
|
134
113
|
end
|
data/lib/ninja_model/base.rb
CHANGED
@@ -1,17 +1,34 @@
|
|
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'
|
12
|
+
require 'active_record/named_scope'
|
13
|
+
require 'active_record/aggregations'
|
14
|
+
|
1
15
|
module NinjaModel
|
2
16
|
class Base
|
3
|
-
include
|
4
|
-
include Callbacks
|
17
|
+
include AttributeMethods
|
5
18
|
include Identity
|
6
19
|
include Persistence
|
7
|
-
include Scoping
|
8
20
|
include Validation
|
9
21
|
include Adapters
|
10
22
|
include Associations
|
11
23
|
include Reflection
|
12
|
-
include ActiveRecord::Aggregations
|
13
24
|
extend ActiveModel::Translation
|
14
25
|
extend ActiveModel::Naming
|
26
|
+
include ActiveModel::Observing
|
27
|
+
include ActiveModel::Dirty
|
28
|
+
include ActiveRecord::Aggregations
|
29
|
+
include ActiveRecord::NamedScope
|
30
|
+
|
31
|
+
define_model_callbacks :initialize, :find, :touch, :only => :after
|
15
32
|
|
16
33
|
class_inheritable_accessor :default_scoping, :instance_writer => false
|
17
34
|
self.default_scoping = []
|
@@ -21,20 +38,6 @@ module NinjaModel
|
|
21
38
|
delegate :find, :first, :last, :all, :exists?, :to => :scoped
|
22
39
|
delegate :where, :order, :limit, :to => :scoped
|
23
40
|
|
24
|
-
def configuration_path
|
25
|
-
@config_path ||= File.join(Rails.root, "config/ninja_model.yml")
|
26
|
-
end
|
27
|
-
|
28
|
-
def configuration_path=(new_path)
|
29
|
-
@config_path = new_path
|
30
|
-
end
|
31
|
-
|
32
|
-
def configuration
|
33
|
-
require 'erb'
|
34
|
-
require 'yaml'
|
35
|
-
@configuration ||= YAML::load(ERB.new(IO.read(configuration_path)).result).with_indifferent_access
|
36
|
-
end
|
37
|
-
|
38
41
|
def relation
|
39
42
|
@relation ||= Relation.new(self)
|
40
43
|
end
|
@@ -98,9 +101,9 @@ module NinjaModel
|
|
98
101
|
@attributes = record.stringify_keys
|
99
102
|
@readonly = @destroyed = false
|
100
103
|
@persisted = true
|
104
|
+
_run_find_callbacks
|
105
|
+
_run_initialize_callbacks
|
101
106
|
self
|
102
107
|
end
|
103
108
|
end
|
104
109
|
end
|
105
|
-
|
106
|
-
require 'ninja_model/core_ext/symbol'
|
@@ -1,14 +1,5 @@
|
|
1
|
-
require 'active_support'
|
2
|
-
require 'active_model/callbacks'
|
3
|
-
|
4
1
|
module NinjaModel
|
5
|
-
|
6
|
-
extend
|
7
|
-
|
8
|
-
included do
|
9
|
-
extend ActiveModel::Callbacks
|
10
|
-
define_model_callbacks :initialize, :find, :touch, :only => :after
|
11
|
-
define_model_callbacks :save, :create, :update, :destroy, :validation
|
12
|
-
end
|
2
|
+
class Base
|
3
|
+
extend ActiveModel::Callbacks
|
13
4
|
end
|
14
5
|
end
|