dm-core 1.1.0.rc2 → 1.1.0.rc3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. data/Gemfile +1 -8
  2. data/VERSION +1 -1
  3. data/dm-core.gemspec +24 -15
  4. data/lib/dm-core.rb +9 -48
  5. data/lib/dm-core/adapters.rb +1 -1
  6. data/lib/dm-core/adapters/abstract_adapter.rb +2 -1
  7. data/lib/dm-core/associations/many_to_many.rb +3 -3
  8. data/lib/dm-core/associations/many_to_one.rb +15 -4
  9. data/lib/dm-core/associations/one_to_many.rb +1 -1
  10. data/lib/dm-core/associations/relationship.rb +7 -6
  11. data/lib/dm-core/collection.rb +7 -2
  12. data/lib/dm-core/core_ext/pathname.rb +0 -14
  13. data/lib/dm-core/ext/array.rb +41 -0
  14. data/lib/dm-core/ext/blank.rb +24 -0
  15. data/lib/dm-core/ext/hash.rb +67 -0
  16. data/lib/dm-core/ext/module.rb +49 -0
  17. data/lib/dm-core/ext/object.rb +57 -0
  18. data/lib/dm-core/ext/singleton_class.rb +8 -0
  19. data/lib/dm-core/ext/string.rb +24 -0
  20. data/lib/dm-core/ext/try_dup.rb +12 -0
  21. data/lib/dm-core/model.rb +2 -1
  22. data/lib/dm-core/model/property.rb +3 -2
  23. data/lib/dm-core/property.rb +1 -1
  24. data/lib/dm-core/property/class.rb +1 -1
  25. data/lib/dm-core/property/date.rb +3 -3
  26. data/lib/dm-core/property/date_time.rb +3 -3
  27. data/lib/dm-core/property/time.rb +3 -3
  28. data/lib/dm-core/property/typecast/time.rb +2 -2
  29. data/lib/dm-core/property_set.rb +1 -1
  30. data/lib/dm-core/query.rb +9 -12
  31. data/lib/dm-core/resource.rb +2 -2
  32. data/lib/dm-core/spec/lib/counter_adapter.rb +1 -1
  33. data/lib/dm-core/spec/lib/spec_helper.rb +1 -1
  34. data/lib/dm-core/spec/shared/resource_spec.rb +2 -1
  35. data/lib/dm-core/support/equalizer.rb +1 -1
  36. data/lib/dm-core/support/hook.rb +0 -15
  37. data/lib/dm-core/support/inflections.rb +60 -0
  38. data/lib/dm-core/support/inflector.rb +3 -0
  39. data/lib/dm-core/support/inflector/inflections.rb +211 -0
  40. data/lib/dm-core/support/inflector/methods.rb +151 -0
  41. data/lib/dm-core/support/lazy_array.rb +4 -4
  42. data/lib/dm-core/support/mash.rb +176 -0
  43. data/lib/dm-core/support/subject.rb +1 -1
  44. data/lib/dm-core/version.rb +1 -1
  45. data/spec/public/model/relationship_spec.rb +2 -1
  46. data/spec/public/shared/collection_shared_spec.rb +5 -2
  47. data/spec/public/shared/finder_shared_spec.rb +10 -6
  48. data/spec/semipublic/adapters/in_memory_adapter_spec.rb +1 -1
  49. data/spec/semipublic/query_spec.rb +2 -2
  50. data/spec/semipublic/resource/state/immutable_spec.rb +2 -1
  51. data/spec/semipublic/resource/state_spec.rb +1 -3
  52. data/spec/semipublic/shared/resource_state_shared_spec.rb +2 -1
  53. data/spec/semipublic/shared/subject_shared_spec.rb +1 -1
  54. data/spec/support/core_ext/hash.rb +10 -0
  55. data/spec/support/core_ext/inheritable_attributes.rb +46 -0
  56. data/spec/unit/array_spec.rb +8 -20
  57. data/spec/unit/blank_spec.rb +62 -0
  58. data/spec/unit/data_mapper/ordered_set/hash_spec.rb +1 -1
  59. data/spec/unit/hash_spec.rb +8 -16
  60. data/spec/unit/lazy_array_spec.rb +3 -14
  61. data/spec/unit/mash_spec.rb +312 -0
  62. data/spec/unit/module_spec.rb +15 -15
  63. data/spec/unit/object_spec.rb +9 -9
  64. data/spec/unit/try_dup_spec.rb +8 -8
  65. metadata +32 -39
  66. data/lib/dm-core/core_ext/array.rb +0 -36
  67. data/lib/dm-core/core_ext/hash.rb +0 -30
  68. data/lib/dm-core/core_ext/module.rb +0 -46
  69. data/lib/dm-core/core_ext/object.rb +0 -31
  70. data/lib/dm-core/core_ext/string.rb +0 -22
  71. data/lib/dm-core/core_ext/try_dup.rb +0 -44
@@ -0,0 +1,24 @@
1
+ module DataMapper
2
+ module Ext
3
+ # Determines whether the specified +value+ is blank.
4
+ #
5
+ # An object is blank if it's false, empty, or a whitespace string.
6
+ # For example, "", " ", +nil+, [], and {} are blank.
7
+ #
8
+ # @api semipublic
9
+ def self.blank?(value)
10
+ case value
11
+ when ::NilClass, ::FalseClass
12
+ true
13
+ when ::TrueClass, ::Numeric
14
+ false
15
+ when ::Array, ::Hash
16
+ value.empty?
17
+ when ::String
18
+ value !~ /\S/
19
+ else
20
+ value.nil? || (value.respond_to?(:empty?) && value.empty?)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,67 @@
1
+ module DataMapper; module Ext
2
+ module Hash
3
+ # Creates a hash with *only* the specified key/value pairs from +hash+.
4
+ #
5
+ # @param [Hash] hash The hash from which to pick the key/value pairs.
6
+ # @param [Array] *keys The hash keys to include.
7
+ #
8
+ # @return [Hash] A new hash with only the selected keys.
9
+ #
10
+ # @example
11
+ # hash = { :one => 1, :two => 2, :three => 3 }
12
+ # Ext::Hash.only(hash, :one, :two) # => { :one => 1, :two => 2 }
13
+ #
14
+ # @api semipublic
15
+ def self.only(hash, *keys)
16
+ h = {}
17
+ keys.each {|k| h[k] = hash[k] if hash.has_key?(k) }
18
+ h
19
+ end
20
+
21
+ # Returns a hash that includes everything but the given +keys+.
22
+ #
23
+ # @param [Hash] hash The hash from which to pick the key/value pairs.
24
+ # @param [Array] *keys The hash keys to exclude.
25
+ #
26
+ # @return [Hash] A new hash without the specified keys.
27
+ #
28
+ # @example
29
+ # hash = { :one => 1, :two => 2, :three => 3 }
30
+ # Ext::Hash.except(hash, :one, :two) # => { :three => 3 }
31
+ #
32
+ # @api semipublic
33
+ def self.except(hash, *keys)
34
+ self.except!(hash.dup, *keys)
35
+ end
36
+
37
+ # Removes the specified +keys+ from the given +hash+.
38
+ #
39
+ # @param [Hash] hash The hash to modify.
40
+ # @param [Array] *keys The hash keys to exclude.
41
+ #
42
+ # @return [Hash] +hash+
43
+ #
44
+ # @example
45
+ # hash = { :one => 1, :two => 2, :three => 3 }
46
+ # Ext::Hash.except!(hash, :one, :two)
47
+ # hash # => { :three => 3 }
48
+ #
49
+ # @api semipublic
50
+ def self.except!(hash, *keys)
51
+ keys.each { |key| hash.delete(key) }
52
+ hash
53
+ end
54
+
55
+ # Converts the specified +hash+ to a {Mash}.
56
+ #
57
+ # @param [Hash] hash The hash to convert.
58
+ # @return [Mash] The {Mash} for the specified +hash+.
59
+ #
60
+ # @api semipublic
61
+ def self.to_mash(hash)
62
+ h = Mash.new(hash)
63
+ h.default = hash.default
64
+ h
65
+ end
66
+ end
67
+ end; end
@@ -0,0 +1,49 @@
1
+ require 'dm-core/ext/object'
2
+
3
+ module DataMapper; module Ext
4
+ module Module
5
+
6
+ # @api semipublic
7
+ def self.find_const(mod, const_name)
8
+ if const_name[0..1] == '::'
9
+ DataMapper::Ext::Object.full_const_get(const_name[2..-1])
10
+ else
11
+ nested_const_lookup(mod, const_name)
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ # Doesn't do any caching since constants can change with remove_const
18
+ def self.nested_const_lookup(mod, const_name)
19
+ unless mod.equal?(::Object)
20
+ constants = []
21
+
22
+ mod.name.split('::').each do |part|
23
+ const = constants.last || ::Object
24
+ constants << const.const_get(part)
25
+ end
26
+
27
+ parts = const_name.split('::')
28
+
29
+ # from most to least specific constant, use each as a base and try
30
+ # to find a constant with the name const_name within them
31
+ constants.reverse_each do |const|
32
+ # return the nested constant if available
33
+ return const if parts.all? do |part|
34
+ const = if RUBY_VERSION >= '1.9.0'
35
+ const.const_defined?(part, false) ? const.const_get(part, false) : nil
36
+ else
37
+ const.const_defined?(part) ? const.const_get(part) : nil
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ # no relative constant found, fallback to an absolute lookup and
44
+ # use const_missing if not found
45
+ DataMapper::Ext::Object.full_const_get(const_name)
46
+ end
47
+
48
+ end
49
+ end; end
@@ -0,0 +1,57 @@
1
+ module DataMapper; module Ext
2
+ module Object
3
+ # Returns the value of the specified constant.
4
+ #
5
+ # @overload full_const_get(obj, name)
6
+ # Returns the value of the specified constant in +obj+.
7
+ # @param [Object] obj The root object used as origin.
8
+ # @param [String] name The name of the constant to get, e.g. "Merb::Router".
9
+ #
10
+ # @overload full_const_get(name)
11
+ # Returns the value of the fully-qualified constant.
12
+ # @param [String] name The name of the constant to get, e.g. "Merb::Router".
13
+ #
14
+ # @return [Object] The constant corresponding to +name+.
15
+ #
16
+ # @api semipublic
17
+ def self.full_const_get(obj, name = nil)
18
+ obj, name = ::Object, obj if name.nil?
19
+
20
+ list = name.split("::")
21
+ list.shift if DataMapper::Ext.blank?(list.first)
22
+ list.each do |x|
23
+ # This is required because const_get tries to look for constants in the
24
+ # ancestor chain, but we only want constants that are HERE
25
+ obj = obj.const_defined?(x) ? obj.const_get(x) : obj.const_missing(x)
26
+ end
27
+ obj
28
+ end
29
+
30
+ # Sets the specified constant to the given +value+.
31
+ #
32
+ # @overload full_const_set(obj, name)
33
+ # Sets the specified constant in +obj+ to the given +value+.
34
+ # @param [Object] obj The root object used as origin.
35
+ # @param [String] name The name of the constant to set, e.g. "Merb::Router".
36
+ # @param [Object] value The value to assign to the constant.
37
+ #
38
+ # @overload full_const_set(name)
39
+ # Sets the fully-qualified constant to the given +value+.
40
+ # @param [String] name The name of the constant to set, e.g. "Merb::Router".
41
+ # @param [Object] value The value to assign to the constant.
42
+ #
43
+ # @return [Object] The constant corresponding to +name+.
44
+ #
45
+ # @api semipublic
46
+ def self.full_const_set(obj, name, value = nil)
47
+ obj, name, value = ::Object, obj, name if value.nil?
48
+
49
+ list = name.split("::")
50
+ toplevel = DataMapper::Ext.blank?(list.first)
51
+ list.shift if toplevel
52
+ last = list.pop
53
+ obj = list.empty? ? ::Object : DataMapper::Ext::Object.full_const_get(list.join("::"))
54
+ obj.const_set(last, value) if obj && !obj.const_defined?(last)
55
+ end
56
+ end
57
+ end; end
@@ -0,0 +1,8 @@
1
+ module Kernel
2
+ # Returns the object's singleton class.
3
+ def singleton_class
4
+ class << self
5
+ self
6
+ end
7
+ end unless respond_to?(:singleton_class) # exists in 1.9.2
8
+ end
@@ -0,0 +1,24 @@
1
+ module DataMapper; module Ext
2
+ module String
3
+ # Replace sequences of whitespace (including newlines) with either
4
+ # a single space or remove them entirely (according to param _spaced_).
5
+ #
6
+ # compress_lines(<<QUERY)
7
+ # SELECT name
8
+ # FROM users
9
+ # QUERY => "SELECT name FROM users"
10
+ #
11
+ # @param [String] string
12
+ # The input string.
13
+ #
14
+ # @param [TrueClass, FalseClass] spaced (default=true)
15
+ # Determines whether returned string has whitespace collapsed or removed.
16
+ #
17
+ # @return [String] The input string with whitespace (including newlines) replaced.
18
+ #
19
+ # @api semipublic
20
+ def self.compress_lines(string, spaced = true)
21
+ string.split($/).map { |line| line.strip }.join(spaced ? ' ' : '')
22
+ end
23
+ end
24
+ end; end
@@ -0,0 +1,12 @@
1
+ module DataMapper
2
+ module Ext
3
+ def self.try_dup(value)
4
+ case value
5
+ when ::TrueClass, ::FalseClass, ::NilClass, ::Module, ::Numeric, ::Symbol
6
+ value
7
+ else
8
+ value.dup
9
+ end
10
+ end
11
+ end
12
+ end
@@ -564,7 +564,8 @@ module DataMapper
564
564
  discriminator = properties(repository_name).discriminator
565
565
  no_reload = !query.reload?
566
566
 
567
- field_map = fields.map { |property| [ property, property.field ] }.to_hash
567
+ field_map = fields.map { |property| [ property, property.field ] }
568
+ field_map = DataMapper::Ext::Array.to_hash(field_map)
568
569
 
569
570
  records.map do |record|
570
571
  identity_map = nil
@@ -65,7 +65,7 @@ module DataMapper
65
65
  # Add property to the other mappings as well if this is for the default
66
66
  # repository.
67
67
  if repository_name == default_repository_name
68
- @properties.except(default_repository_name).each do |other_repository_name, properties|
68
+ DataMapper::Ext::Hash.except(@properties, default_repository_name).each do |other_repository_name, properties|
69
69
  next if properties.named?(name)
70
70
 
71
71
  # make sure the property is created within the correct repository scope
@@ -175,7 +175,8 @@ module DataMapper
175
175
 
176
176
  # @api private
177
177
  def key_conditions(repository, key)
178
- self.key(repository.name).zip(key.nil? ? [] : key).to_hash
178
+ conditions = self.key(repository.name).zip(key.nil? ? [] : key)
179
+ DataMapper::Ext::Array.to_hash(conditions)
179
180
  end
180
181
 
181
182
  private
@@ -764,7 +764,7 @@ module DataMapper
764
764
  @instance_variable_name = "@#{@name}".freeze
765
765
 
766
766
  @primitive = self.class.primitive
767
- @field = @options[:field].freeze
767
+ @field = @options[:field].freeze unless @options[:field].nil?
768
768
  @default = @options[:default]
769
769
 
770
770
  @serial = @options.fetch(:serial, false)
@@ -15,7 +15,7 @@ module DataMapper
15
15
  #
16
16
  # @api private
17
17
  def typecast_to_primitive(value)
18
- model.find_const(value.to_s)
18
+ DataMapper::Ext::Module.find_const(model, value.to_s)
19
19
  rescue NameError
20
20
  value
21
21
  end
@@ -11,7 +11,7 @@ module DataMapper
11
11
  # Typecasts an arbitrary value to a Date
12
12
  # Handles both Hashes and Date instances.
13
13
  #
14
- # @param [#to_mash, #to_s] value
14
+ # @param [Hash, #to_mash, #to_s] value
15
15
  # value to be typecast
16
16
  #
17
17
  # @return [Date]
@@ -21,7 +21,7 @@ module DataMapper
21
21
  def typecast_to_primitive(value)
22
22
  if value.respond_to?(:to_date)
23
23
  value.to_date
24
- elsif value.respond_to?(:to_mash)
24
+ elsif value.is_a?(::Hash) || value.respond_to?(:to_mash)
25
25
  typecast_hash_to_date(value)
26
26
  else
27
27
  ::Date.parse(value.to_s)
@@ -32,7 +32,7 @@ module DataMapper
32
32
 
33
33
  # Creates a Date instance from a Hash with keys :year, :month, :day
34
34
  #
35
- # @param [#to_mash] value
35
+ # @param [Hash, #to_mash] value
36
36
  # value to be typecast
37
37
  #
38
38
  # @return [Date]
@@ -11,7 +11,7 @@ module DataMapper
11
11
  # Typecasts an arbitrary value to a DateTime.
12
12
  # Handles both Hashes and DateTime instances.
13
13
  #
14
- # @param [#to_mash, #to_s] value
14
+ # @param [Hash, #to_mash, #to_s] value
15
15
  # value to be typecast
16
16
  #
17
17
  # @return [DateTime]
@@ -19,7 +19,7 @@ module DataMapper
19
19
  #
20
20
  # @api private
21
21
  def typecast_to_primitive(value)
22
- if value.respond_to?(:to_mash)
22
+ if value.is_a?(::Hash) || value.respond_to?(:to_mash)
23
23
  typecast_hash_to_datetime(value)
24
24
  else
25
25
  ::DateTime.parse(value.to_s)
@@ -31,7 +31,7 @@ module DataMapper
31
31
  # Creates a DateTime instance from a Hash with keys :year, :month, :day,
32
32
  # :hour, :min, :sec
33
33
  #
34
- # @param [#to_mash] value
34
+ # @param [Hash, #to_mash] value
35
35
  # value to be typecast
36
36
  #
37
37
  # @return [DateTime]
@@ -11,7 +11,7 @@ module DataMapper
11
11
  # Typecasts an arbitrary value to a Time
12
12
  # Handles both Hashes and Time instances.
13
13
  #
14
- # @param [#to_mash, #to_s] value
14
+ # @param [Hash, #to_mash, #to_s] value
15
15
  # value to be typecast
16
16
  #
17
17
  # @return [Time]
@@ -21,7 +21,7 @@ module DataMapper
21
21
  def typecast_to_primitive(value)
22
22
  if value.respond_to?(:to_time)
23
23
  value.to_time
24
- elsif value.respond_to?(:to_mash)
24
+ elsif value.is_a?(::Hash) || value.respond_to?(:to_mash)
25
25
  typecast_hash_to_time(value)
26
26
  else
27
27
  ::Time.parse(value.to_s)
@@ -33,7 +33,7 @@ module DataMapper
33
33
  # Creates a Time instance from a Hash with keys :year, :month, :day,
34
34
  # :hour, :min, :sec
35
35
  #
36
- # @param [#to_mash] value
36
+ # @param [Hash, #to_mash] value
37
37
  # value to be typecast
38
38
  #
39
39
  # @return [Time]
@@ -7,7 +7,7 @@ module DataMapper
7
7
  # Extracts the given args from the hash. If a value does not exist, it
8
8
  # uses the value of Time.now.
9
9
  #
10
- # @param [#to_mash] value
10
+ # @param [Hash, #to_mash] value
11
11
  # value to extract time args from
12
12
  #
13
13
  # @return [Array]
@@ -15,7 +15,7 @@ module DataMapper
15
15
  #
16
16
  # @api private
17
17
  def extract_time(value)
18
- mash = value.to_mash
18
+ mash = value.respond_to?(:to_mash) ? value.to_mash : DataMapper::Ext::Hash.to_mash(value)
19
19
  now = ::Time.now
20
20
 
21
21
  [ :year, :month, :day, :hour, :min, :sec ].map do |segment|
@@ -144,7 +144,7 @@ module DataMapper
144
144
 
145
145
  # @api private
146
146
  def field_map
147
- map { |property| [ property.field, property ] }.to_hash
147
+ DataMapper::Ext::Array.to_hash(map { |property| [ property.field, property ] })
148
148
  end
149
149
 
150
150
  def inspect
@@ -362,12 +362,12 @@ module DataMapper
362
362
  @options = @options.merge(other_options).freeze
363
363
  assert_valid_options(@options)
364
364
 
365
- normalize = other_options.only(*OPTIONS - [ :conditions ]).map do |attribute, value|
366
- instance_variable_set("@#{attribute}", value.try_dup)
365
+ normalize = DataMapper::Ext::Hash.only(other_options, *OPTIONS - [ :conditions ]).map do |attribute, value|
366
+ instance_variable_set("@#{attribute}", DataMapper::Ext.try_dup(value))
367
367
  attribute
368
368
  end
369
369
 
370
- merge_conditions([ other_options.except(*OPTIONS), other_options[:conditions] ])
370
+ merge_conditions([ DataMapper::Ext::Hash.except(other_options, *OPTIONS), other_options[:conditions] ])
371
371
  normalize_options(normalize | [ :links, :unique ])
372
372
 
373
373
  self
@@ -689,7 +689,7 @@ module DataMapper
689
689
  #
690
690
  # @api private
691
691
  def to_relative_hash
692
- to_hash.only(:fields, :order, :unique, :add_reversed, :reload)
692
+ DataMapper::Ext::Hash.only(to_hash, :fields, :order, :unique, :add_reversed, :reload)
693
693
  end
694
694
 
695
695
  private
@@ -737,7 +737,7 @@ module DataMapper
737
737
  @reload = @options.fetch :reload, false
738
738
  @raw = false
739
739
 
740
- merge_conditions([ @options.except(*OPTIONS), @options[:conditions] ])
740
+ merge_conditions([ DataMapper::Ext::Hash.except(@options, *OPTIONS), @options[:conditions] ])
741
741
  normalize_options
742
742
  end
743
743
 
@@ -748,7 +748,7 @@ module DataMapper
748
748
  @fields = @fields.dup
749
749
  @links = @links.dup
750
750
  @conditions = @conditions.dup
751
- @order = @order.try_dup
751
+ @order = DataMapper::Ext.try_dup(@order)
752
752
  end
753
753
 
754
754
  # Validate the options
@@ -855,12 +855,9 @@ module DataMapper
855
855
  when Symbol, ::String
856
856
  original = subject
857
857
  subject = subject.to_s
858
+ name = subject[0, subject.index('.') || subject.length]
858
859
 
859
- if subject.include?('.')
860
- unless @relationships.named?(subject[0, subject.index('.')])
861
- raise ArgumentError, "condition #{original.inspect} does not map to a relationship in #{model}"
862
- end
863
- elsif !@properties.named?(subject) && !@relationships.named?(subject)
860
+ unless @properties.named?(name) || @relationships.named?(name)
864
861
  raise ArgumentError, "condition #{original.inspect} does not map to a property or relationship in #{model}"
865
862
  end
866
863
 
@@ -899,7 +896,7 @@ module DataMapper
899
896
 
900
897
  first_condition = conditions.first
901
898
 
902
- unless first_condition.kind_of?(String) && !first_condition.blank?
899
+ unless first_condition.kind_of?(String) && !DataMapper::Ext.blank?(first_condition)
903
900
  raise ArgumentError, '+options[:conditions]+ should have a statement for the first entry'
904
901
  end
905
902
  end