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.
Files changed (54) hide show
  1. data/.gitignore +19 -0
  2. data/Rakefile +0 -7
  3. data/autotest/discover.rb +1 -0
  4. data/lib/ninja_model.rb +22 -26
  5. data/lib/ninja_model/adapters.rb +33 -43
  6. data/lib/ninja_model/adapters/abstract_adapter.rb +2 -10
  7. data/lib/ninja_model/adapters/adapter_manager.rb +17 -10
  8. data/lib/ninja_model/adapters/adapter_pool.rb +15 -17
  9. data/lib/ninja_model/adapters/adapter_specification.rb +3 -3
  10. data/lib/ninja_model/associations.rb +25 -106
  11. data/lib/ninja_model/associations/association_proxy.rb +119 -1
  12. data/lib/ninja_model/associations/belongs_to_association.rb +5 -1
  13. data/lib/ninja_model/attribute.rb +130 -0
  14. data/lib/ninja_model/{attributes.rb → attribute_methods.rb} +21 -42
  15. data/lib/ninja_model/base.rb +23 -20
  16. data/lib/ninja_model/callbacks.rb +2 -11
  17. data/lib/ninja_model/identity.rb +6 -11
  18. data/lib/ninja_model/persistence.rb +15 -30
  19. data/lib/ninja_model/predicate.rb +4 -4
  20. data/lib/ninja_model/rails_ext/active_record.rb +187 -0
  21. data/lib/ninja_model/railtie.rb +14 -8
  22. data/lib/ninja_model/reflection.rb +7 -14
  23. data/lib/ninja_model/relation.rb +5 -3
  24. data/lib/ninja_model/relation/finder_methods.rb +4 -8
  25. data/lib/ninja_model/relation/spawn_methods.rb +1 -1
  26. data/lib/ninja_model/validation.rb +6 -23
  27. data/lib/ninja_model/version.rb +1 -1
  28. data/ninja-model.gemspec +28 -0
  29. data/spec/ninja_model/adapters/abstract_adapter_spec.rb +45 -0
  30. data/spec/ninja_model/adapters/adapter_manager_spec.rb +69 -0
  31. data/spec/ninja_model/adapters/adapter_pool_spec.rb +210 -48
  32. data/spec/ninja_model/adapters_spec.rb +77 -0
  33. data/spec/ninja_model/attribute_methods_spec.rb +95 -0
  34. data/spec/ninja_model/attribute_spec.rb +129 -0
  35. data/spec/ninja_model/base_spec.rb +4 -52
  36. data/spec/ninja_model/identity_spec.rb +16 -32
  37. data/spec/ninja_model/persistence_spec.rb +130 -4
  38. data/spec/ninja_model/predicate_spec.rb +40 -6
  39. data/spec/ninja_model/query_methods_spec.rb +76 -74
  40. data/spec/ninja_model/reflection_spec.rb +63 -0
  41. data/spec/ninja_model/relation_spec.rb +213 -20
  42. data/spec/ninja_model/symbol_spec.rb +19 -0
  43. data/spec/ninja_model/validation_spec.rb +18 -0
  44. data/spec/spec_helper.rb +9 -0
  45. data/spec/support/matchers/convert.rb +30 -0
  46. metadata +85 -63
  47. data/lib/ninja_model/associations/active_record_proxy.rb +0 -53
  48. data/lib/ninja_model/associations/ninja_model_proxy.rb +0 -46
  49. data/lib/ninja_model/configuration.rb +0 -20
  50. data/lib/ninja_model/errors.rb +0 -5
  51. data/lib/ninja_model/log_subscriber.rb +0 -18
  52. data/lib/ninja_model/scoping.rb +0 -50
  53. data/spec/ninja_model/attributes_spec.rb +0 -85
  54. 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 < ActiveRecord::Associations::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
- @reflection.klass.scoped.where(@reflection.association_foreign_key => @owner.send(@reflection.primary_key_name)).first
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 Attribute
6
- attr_reader :name, :type, :default, :primary_key
7
- alias :primary_key? :primary_key
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
- def convert(value)
23
- case type
24
- when :string then value
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
- module Attributes
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, default, self, opts)
18
+ new_attr = Attribute.new(name, data_type, opts)
44
19
  self.model_attributes << new_attr
45
- new_attr.define_methods!
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
- included do
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
- write_attribute(name, value)
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
@@ -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 Attributes
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
- module Callbacks
6
- extend ActiveSupport::Concern
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