dm-core 1.1.0.rc2 → 1.1.0.rc3

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 (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