sbf-dm-types 1.3.0.beta

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 (76) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +45 -0
  3. data/.rspec +1 -0
  4. data/.rubocop.yml +468 -0
  5. data/.travis.yml +52 -0
  6. data/Gemfile +67 -0
  7. data/LICENSE +20 -0
  8. data/README.rdoc +3 -0
  9. data/Rakefile +4 -0
  10. data/dm-types.gemspec +26 -0
  11. data/lib/dm-types/api_key.rb +30 -0
  12. data/lib/dm-types/bcrypt_hash.rb +33 -0
  13. data/lib/dm-types/comma_separated_list.rb +28 -0
  14. data/lib/dm-types/csv.rb +34 -0
  15. data/lib/dm-types/enum.rb +55 -0
  16. data/lib/dm-types/epoch_time.rb +35 -0
  17. data/lib/dm-types/file_path.rb +24 -0
  18. data/lib/dm-types/flag.rb +64 -0
  19. data/lib/dm-types/ip_address.rb +34 -0
  20. data/lib/dm-types/json.rb +48 -0
  21. data/lib/dm-types/paranoid/base.rb +56 -0
  22. data/lib/dm-types/paranoid_boolean.rb +23 -0
  23. data/lib/dm-types/paranoid_datetime.rb +22 -0
  24. data/lib/dm-types/regexp.rb +21 -0
  25. data/lib/dm-types/slug.rb +28 -0
  26. data/lib/dm-types/support/dirty_minder.rb +168 -0
  27. data/lib/dm-types/support/flags.rb +41 -0
  28. data/lib/dm-types/uri.rb +27 -0
  29. data/lib/dm-types/uuid.rb +64 -0
  30. data/lib/dm-types/version.rb +5 -0
  31. data/lib/dm-types/yaml.rb +39 -0
  32. data/lib/dm-types.rb +23 -0
  33. data/spec/fixtures/api_user.rb +14 -0
  34. data/spec/fixtures/article.rb +35 -0
  35. data/spec/fixtures/bookmark.rb +23 -0
  36. data/spec/fixtures/invention.rb +7 -0
  37. data/spec/fixtures/network_node.rb +36 -0
  38. data/spec/fixtures/person.rb +25 -0
  39. data/spec/fixtures/software_package.rb +33 -0
  40. data/spec/fixtures/ticket.rb +21 -0
  41. data/spec/fixtures/tshirt.rb +24 -0
  42. data/spec/integration/api_key_spec.rb +27 -0
  43. data/spec/integration/bcrypt_hash_spec.rb +47 -0
  44. data/spec/integration/comma_separated_list_spec.rb +87 -0
  45. data/spec/integration/dirty_minder_spec.rb +197 -0
  46. data/spec/integration/enum_spec.rb +78 -0
  47. data/spec/integration/epoch_time_spec.rb +61 -0
  48. data/spec/integration/file_path_spec.rb +160 -0
  49. data/spec/integration/flag_spec.rb +72 -0
  50. data/spec/integration/ip_address_spec.rb +153 -0
  51. data/spec/integration/json_spec.rb +72 -0
  52. data/spec/integration/slug_spec.rb +67 -0
  53. data/spec/integration/uri_spec.rb +117 -0
  54. data/spec/integration/uuid_spec.rb +102 -0
  55. data/spec/integration/yaml_spec.rb +87 -0
  56. data/spec/shared/flags_shared_spec.rb +37 -0
  57. data/spec/shared/identity_function_group.rb +5 -0
  58. data/spec/spec_helper.rb +30 -0
  59. data/spec/unit/bcrypt_hash_spec.rb +155 -0
  60. data/spec/unit/csv_spec.rb +142 -0
  61. data/spec/unit/dirty_minder_spec.rb +65 -0
  62. data/spec/unit/enum_spec.rb +126 -0
  63. data/spec/unit/epoch_time_spec.rb +74 -0
  64. data/spec/unit/file_path_spec.rb +75 -0
  65. data/spec/unit/flag_spec.rb +114 -0
  66. data/spec/unit/ip_address_spec.rb +109 -0
  67. data/spec/unit/json_spec.rb +126 -0
  68. data/spec/unit/paranoid_boolean_spec.rb +149 -0
  69. data/spec/unit/paranoid_datetime_spec.rb +153 -0
  70. data/spec/unit/regexp_spec.rb +63 -0
  71. data/spec/unit/uri_spec.rb +83 -0
  72. data/spec/unit/yaml_spec.rb +111 -0
  73. data/tasks/spec.rake +21 -0
  74. data/tasks/yard.rake +9 -0
  75. data/tasks/yardstick.rake +19 -0
  76. metadata +229 -0
@@ -0,0 +1,30 @@
1
+ require 'dm-core'
2
+
3
+ require 'digest/sha1'
4
+
5
+ module DataMapper
6
+ class Property
7
+ class APIKey < String
8
+
9
+ # The amount of random seed data to use to generate tha API Key
10
+ PADDING = 256
11
+
12
+ length 40
13
+ unique true
14
+ default proc { APIKey.generate }
15
+
16
+ #
17
+ # Generates a new API Key.
18
+ #
19
+ # @return [String]
20
+ # The new API Key.
21
+ #
22
+ def self.generate
23
+ sha1 = Digest::SHA1.new
24
+
25
+ PADDING.times { sha1 << rand(256).chr }
26
+ sha1.hexdigest
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,33 @@
1
+ require 'dm-core'
2
+ require 'bcrypt'
3
+
4
+ module DataMapper
5
+ class Property
6
+ class BCryptHash < String
7
+ load_as BCrypt::Password
8
+
9
+ length 60
10
+
11
+ def load(value)
12
+ typecast(value)
13
+ end
14
+
15
+ def dump(value)
16
+ hash = typecast(value)
17
+ return if hash.nil?
18
+
19
+ hash_string = hash.to_s
20
+ hash_string.encode!('UTF-8') if hash_string.respond_to?(:encode!)
21
+ hash_string
22
+ end
23
+
24
+ def typecast(value)
25
+ return value if value.nil? || value.kind_of?(BCrypt::Password)
26
+
27
+ BCrypt::Password.new(value)
28
+ rescue BCrypt::Errors::InvalidHash
29
+ BCrypt::Password.create(value)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,28 @@
1
+ require 'dm-core'
2
+ require 'dm-types/yaml'
3
+
4
+ module DataMapper
5
+ class Property
6
+ class CommaSeparatedList < Yaml
7
+
8
+ def dump(value)
9
+ if value.nil?
10
+ nil
11
+ elsif value.is_a?(::Array)
12
+ super(value)
13
+ elsif value.is_a?(::String)
14
+ v = []
15
+
16
+ value.split(',').each do |element|
17
+ element.strip!
18
+ v << element unless element.empty?
19
+ end
20
+
21
+ super(v)
22
+ else
23
+ raise ArgumentError, "+value+ of CommaSeparatedList must be a string, an array or nil, but given #{value.inspect}"
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,34 @@
1
+ require 'dm-core'
2
+ require 'dm-types/support/dirty_minder'
3
+
4
+ if RUBY_VERSION >= '1.9.0'
5
+ require 'csv'
6
+ else
7
+ require 'fastercsv' # must be ~>1.5
8
+ CSV = FasterCSV unless defined?(CSV)
9
+ end
10
+
11
+ module DataMapper
12
+ class Property
13
+ class Csv < String
14
+ load_as ::Array
15
+
16
+ def load(value)
17
+ case value
18
+ when ::String then CSV.parse(value)
19
+ when ::Array then value
20
+ end
21
+ end
22
+
23
+ def dump(value)
24
+ case value
25
+ when ::Array
26
+ CSV.generate { |csv| value.each { |row| csv << row } }
27
+ when ::String then value
28
+ end
29
+ end
30
+
31
+ include ::DataMapper::Property::DirtyMinder
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,55 @@
1
+ require 'dm-core'
2
+ require 'dm-types/support/flags'
3
+
4
+ module DataMapper
5
+ class Property
6
+ class Enum < Integer
7
+ min 1
8
+
9
+ include Flags
10
+
11
+ load_as ::Object
12
+ dump_as ::Integer
13
+
14
+ def initialize(model, name, options = {})
15
+ @flag_map = {}
16
+
17
+ flags = options.fetch(:flags, self.class.flags)
18
+ flags.each_with_index do |flag, i|
19
+ @flag_map[i.succ] = flag
20
+ end
21
+
22
+ if self.class.accepted_options.include?(:set) && !options.include?(:set)
23
+ options[:set] = @flag_map.values_at(*@flag_map.keys.sort)
24
+ end
25
+
26
+ options[:max] = @flag_map.size
27
+
28
+ super
29
+ end
30
+
31
+ def load(value)
32
+ flag_map[value]
33
+ end
34
+
35
+ def dump(value)
36
+ case value
37
+ when ::Array then value.collect { |v| dump(v) }
38
+ else flag_map.invert[value]
39
+ end
40
+ end
41
+
42
+ def typecast(value)
43
+ return if value.nil?
44
+
45
+ # Attempt to typecast using the class of the first item in the map.
46
+ case flag_map[1]
47
+ when ::Symbol then value.to_sym
48
+ when ::String then value.to_s
49
+ when ::Integer then value.to_i
50
+ else value
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,35 @@
1
+ require 'dm-core'
2
+
3
+ module DataMapper
4
+ class Property
5
+ class EpochTime < Time
6
+ dump_as ::Integer
7
+
8
+ def load(value)
9
+ if value.kind_of?(::Numeric)
10
+ ::Time.at(value.to_i)
11
+ else
12
+ value
13
+ end
14
+ end
15
+
16
+ def dump(value)
17
+ value&.to_i
18
+ end
19
+
20
+ def typecast(value)
21
+ case value
22
+ when ::Time then value
23
+ when ::Numeric, /\A\d+\z/ then ::Time.at(value.to_i)
24
+ when ::DateTime then datetime_to_time(value)
25
+ when ::String then ::Time.parse(value)
26
+ end
27
+ end
28
+
29
+ private def datetime_to_time(datetime)
30
+ utc = datetime.new_offset(0)
31
+ ::Time.utc(utc.year, utc.month, utc.day, utc.hour, utc.min, utc.sec)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,24 @@
1
+ require 'pathname'
2
+ require 'dm-core'
3
+
4
+ module DataMapper
5
+ class Property
6
+ class FilePath < String
7
+ load_as Pathname
8
+
9
+ length 255
10
+
11
+ def load(value)
12
+ Pathname.new(value) unless DataMapper::Ext.blank?(value)
13
+ end
14
+
15
+ def dump(value)
16
+ value.to_s unless DataMapper::Ext.blank?(value)
17
+ end
18
+
19
+ def typecast(value)
20
+ load(value)
21
+ end
22
+ end # class FilePath
23
+ end # class Property
24
+ end # module DataMapper
@@ -0,0 +1,64 @@
1
+ require 'dm-core'
2
+ require 'dm-types/support/flags'
3
+
4
+ module DataMapper
5
+ class Property
6
+ class Flag < Object
7
+ include Flags
8
+
9
+ load_as ::Object
10
+ dump_as ::Integer
11
+
12
+ def initialize(model, name, options = {})
13
+ super
14
+
15
+ @flag_map = {}
16
+
17
+ flags = options.fetch(:flags, self.class.flags)
18
+ flags.each_with_index do |flag, i|
19
+ flag_map[i] = flag
20
+ end
21
+ end
22
+
23
+ def load(value)
24
+ return [] if value.nil? || value <= 0
25
+
26
+ begin
27
+ matches = []
28
+
29
+ 0.upto(flag_map.size - 1) do |i|
30
+ matches << flag_map[i] if value[i] == 1
31
+ end
32
+
33
+ matches.compact
34
+ rescue TypeError, Errno::EDOM
35
+ []
36
+ end
37
+ end
38
+
39
+ def dump(value)
40
+ unless value.nil?
41
+ flags = Array(value).map { |flag| flag.to_sym }
42
+ flags.uniq!
43
+
44
+ flag = 0
45
+
46
+ flag_map.invert.values_at(*flags).each do |i|
47
+ next if i.nil?
48
+ flag += (1 << i)
49
+ end
50
+
51
+ flag
52
+ end
53
+ end
54
+
55
+ def typecast(value)
56
+ case value
57
+ when nil then nil
58
+ when ::Array then value.map { |v| v.to_sym }
59
+ else [value.to_sym]
60
+ end
61
+ end
62
+ end # class Flag
63
+ end # class Property
64
+ end # module DataMapper
@@ -0,0 +1,34 @@
1
+ require 'ipaddr'
2
+ require 'dm-core'
3
+
4
+ module DataMapper
5
+ class Property
6
+ class IPAddress < String
7
+ load_as IPAddr
8
+
9
+ length 39
10
+
11
+ def load(value)
12
+ if value.nil? || value_loaded?(value)
13
+ value
14
+ elsif value.is_a?(::String)
15
+ if value.empty?
16
+ IPAddr.new("0.0.0.0")
17
+ else
18
+ IPAddr.new(value)
19
+ end
20
+ else
21
+ raise ArgumentError.new("+value+ must be nil or a String")
22
+ end
23
+ end
24
+
25
+ def dump(value)
26
+ value.to_s unless value.nil?
27
+ end
28
+
29
+ def typecast(value)
30
+ load(value) unless value.nil?
31
+ end
32
+ end # class IPAddress
33
+ end # module Property
34
+ end # module DataMapper
@@ -0,0 +1,48 @@
1
+ require 'dm-core'
2
+ require 'dm-types/support/dirty_minder'
3
+ require 'multi_json'
4
+
5
+ module DataMapper
6
+ class Property
7
+ class Json < Text
8
+ load_as ::Object
9
+
10
+ def load(value)
11
+ if value.nil? || value_loaded?(value)
12
+ value
13
+ elsif value.is_a?(::String)
14
+ typecast(value)
15
+ else
16
+ raise ArgumentError.new("+value+ of a property of JSON type must be nil or a String")
17
+ end
18
+ end
19
+
20
+ def dump(value)
21
+ if value.nil? || value.is_a?(::String)
22
+ value
23
+ else
24
+ MultiJson.dump(value)
25
+ end
26
+ end
27
+
28
+ def typecast(value)
29
+ return if value.nil?
30
+
31
+ if value_loaded?(value)
32
+ value
33
+ else
34
+ MultiJson.load(value.to_s)
35
+ end
36
+ end
37
+
38
+ def value_loaded?(value)
39
+ value.kind_of?(::Array) || value.kind_of?(::Hash)
40
+ end
41
+
42
+ include ::DataMapper::Property::DirtyMinder
43
+
44
+ end # class Json
45
+
46
+ JSON = Json
47
+ end # class Property
48
+ end # module DataMapper
@@ -0,0 +1,56 @@
1
+ module DataMapper
2
+ module Types
3
+ module Paranoid
4
+ module Base
5
+ def self.included(model)
6
+ model.extend ClassMethods
7
+ model.instance_variable_set(:@paranoid_properties, {})
8
+ end
9
+
10
+ def paranoid_destroy
11
+ model.paranoid_properties.each do |name, block|
12
+ attribute_set(name, block.call(self))
13
+ end
14
+ save_self
15
+ self.persistence_state = Resource::PersistenceState::Immutable.new(self)
16
+ true
17
+ end
18
+
19
+ private
20
+
21
+ # @api private
22
+ def _destroy(execute_hooks = true)
23
+ return false unless saved?
24
+
25
+ if execute_hooks
26
+ paranoid_destroy
27
+ else
28
+ super
29
+ end
30
+ end
31
+ end
32
+
33
+ module ClassMethods
34
+ def inherited(model)
35
+ model.instance_variable_set(:@paranoid_properties, @paranoid_properties.dup)
36
+ super
37
+ end
38
+
39
+ # @api public
40
+ def with_deleted
41
+ with_exclusive_scope({}) { block_given? ? yield : all }
42
+ end
43
+
44
+ # @api private
45
+ def paranoid_properties
46
+ @paranoid_properties
47
+ end
48
+
49
+ # @api private
50
+ def set_paranoid_property(name, &block)
51
+ paranoid_properties[name] = block
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,23 @@
1
+ require 'dm-types/paranoid/base'
2
+
3
+ module DataMapper
4
+ class Property
5
+ class ParanoidBoolean < Boolean
6
+ default false
7
+ lazy true
8
+
9
+ # @api private
10
+ def bind
11
+ property_name = name.inspect
12
+
13
+ model.class_eval <<-RUBY, __FILE__, __LINE__ + 1
14
+ include DataMapper::Types::Paranoid::Base
15
+
16
+ set_paranoid_property(#{property_name}) { true }
17
+
18
+ default_scope(#{repository_name.inspect}).update(#{property_name} => false)
19
+ RUBY
20
+ end
21
+ end # class ParanoidBoolean
22
+ end # module Property
23
+ end # module DataMapper
@@ -0,0 +1,22 @@
1
+ require 'dm-types/paranoid/base'
2
+
3
+ module DataMapper
4
+ class Property
5
+ class ParanoidDateTime < DateTime
6
+ lazy true
7
+
8
+ # @api private
9
+ def bind
10
+ property_name = name.inspect
11
+
12
+ model.class_eval <<-RUBY, __FILE__, __LINE__ + 1
13
+ include DataMapper::Types::Paranoid::Base
14
+
15
+ set_paranoid_property(#{property_name}) { ::DateTime.now }
16
+
17
+ default_scope(#{repository_name.inspect}).update(#{property_name} => nil)
18
+ RUBY
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,21 @@
1
+ require 'dm-core'
2
+
3
+ module DataMapper
4
+ class Property
5
+ class Regexp < String
6
+ load_as ::Regexp
7
+
8
+ def load(value)
9
+ ::Regexp.new(value) unless value.nil?
10
+ end
11
+
12
+ def dump(value)
13
+ value.source unless value.nil?
14
+ end
15
+
16
+ def typecast(value)
17
+ load(value)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,28 @@
1
+ require 'dm-core'
2
+ require 'stringex'
3
+
4
+ module DataMapper
5
+ class Property
6
+ class Slug < String
7
+
8
+ # Maximum length chosen because URI type is limited to 2000
9
+ # characters, and a slug is a component of a URI, so it should
10
+ # not exceed the maximum URI length either.
11
+ length 2000
12
+
13
+ def typecast(value)
14
+ if value.nil?
15
+ nil
16
+ elsif value.respond_to?(:to_str)
17
+ escape(value.to_str)
18
+ else
19
+ raise ArgumentError, '+value+ must be nil or respond to #to_str'
20
+ end
21
+ end
22
+
23
+ def escape(string)
24
+ string.to_url
25
+ end
26
+ end # class Slug
27
+ end # class Property
28
+ end # module DataMapper