ninja-model 0.4.2 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
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