sbf-dm-types 1.3.0.beta
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +45 -0
- data/.rspec +1 -0
- data/.rubocop.yml +468 -0
- data/.travis.yml +52 -0
- data/Gemfile +67 -0
- data/LICENSE +20 -0
- data/README.rdoc +3 -0
- data/Rakefile +4 -0
- data/dm-types.gemspec +26 -0
- data/lib/dm-types/api_key.rb +30 -0
- data/lib/dm-types/bcrypt_hash.rb +33 -0
- data/lib/dm-types/comma_separated_list.rb +28 -0
- data/lib/dm-types/csv.rb +34 -0
- data/lib/dm-types/enum.rb +55 -0
- data/lib/dm-types/epoch_time.rb +35 -0
- data/lib/dm-types/file_path.rb +24 -0
- data/lib/dm-types/flag.rb +64 -0
- data/lib/dm-types/ip_address.rb +34 -0
- data/lib/dm-types/json.rb +48 -0
- data/lib/dm-types/paranoid/base.rb +56 -0
- data/lib/dm-types/paranoid_boolean.rb +23 -0
- data/lib/dm-types/paranoid_datetime.rb +22 -0
- data/lib/dm-types/regexp.rb +21 -0
- data/lib/dm-types/slug.rb +28 -0
- data/lib/dm-types/support/dirty_minder.rb +168 -0
- data/lib/dm-types/support/flags.rb +41 -0
- data/lib/dm-types/uri.rb +27 -0
- data/lib/dm-types/uuid.rb +64 -0
- data/lib/dm-types/version.rb +5 -0
- data/lib/dm-types/yaml.rb +39 -0
- data/lib/dm-types.rb +23 -0
- data/spec/fixtures/api_user.rb +14 -0
- data/spec/fixtures/article.rb +35 -0
- data/spec/fixtures/bookmark.rb +23 -0
- data/spec/fixtures/invention.rb +7 -0
- data/spec/fixtures/network_node.rb +36 -0
- data/spec/fixtures/person.rb +25 -0
- data/spec/fixtures/software_package.rb +33 -0
- data/spec/fixtures/ticket.rb +21 -0
- data/spec/fixtures/tshirt.rb +24 -0
- data/spec/integration/api_key_spec.rb +27 -0
- data/spec/integration/bcrypt_hash_spec.rb +47 -0
- data/spec/integration/comma_separated_list_spec.rb +87 -0
- data/spec/integration/dirty_minder_spec.rb +197 -0
- data/spec/integration/enum_spec.rb +78 -0
- data/spec/integration/epoch_time_spec.rb +61 -0
- data/spec/integration/file_path_spec.rb +160 -0
- data/spec/integration/flag_spec.rb +72 -0
- data/spec/integration/ip_address_spec.rb +153 -0
- data/spec/integration/json_spec.rb +72 -0
- data/spec/integration/slug_spec.rb +67 -0
- data/spec/integration/uri_spec.rb +117 -0
- data/spec/integration/uuid_spec.rb +102 -0
- data/spec/integration/yaml_spec.rb +87 -0
- data/spec/shared/flags_shared_spec.rb +37 -0
- data/spec/shared/identity_function_group.rb +5 -0
- data/spec/spec_helper.rb +30 -0
- data/spec/unit/bcrypt_hash_spec.rb +155 -0
- data/spec/unit/csv_spec.rb +142 -0
- data/spec/unit/dirty_minder_spec.rb +65 -0
- data/spec/unit/enum_spec.rb +126 -0
- data/spec/unit/epoch_time_spec.rb +74 -0
- data/spec/unit/file_path_spec.rb +75 -0
- data/spec/unit/flag_spec.rb +114 -0
- data/spec/unit/ip_address_spec.rb +109 -0
- data/spec/unit/json_spec.rb +126 -0
- data/spec/unit/paranoid_boolean_spec.rb +149 -0
- data/spec/unit/paranoid_datetime_spec.rb +153 -0
- data/spec/unit/regexp_spec.rb +63 -0
- data/spec/unit/uri_spec.rb +83 -0
- data/spec/unit/yaml_spec.rb +111 -0
- data/tasks/spec.rake +21 -0
- data/tasks/yard.rake +9 -0
- data/tasks/yardstick.rake +19 -0
- 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
|
data/lib/dm-types/csv.rb
ADDED
@@ -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
|