dm-types 0.10.2 → 1.0.0.rc1

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 (58) hide show
  1. data/.gitignore +36 -0
  2. data/Gemfile +147 -0
  3. data/Rakefile +7 -8
  4. data/VERSION +1 -1
  5. data/dm-types.gemspec +61 -20
  6. data/lib/dm-types.rb +24 -19
  7. data/lib/dm-types/bcrypt_hash.rb +17 -13
  8. data/lib/dm-types/comma_separated_list.rb +11 -16
  9. data/lib/dm-types/csv.rb +11 -11
  10. data/lib/dm-types/enum.rb +33 -50
  11. data/lib/dm-types/epoch_time.rb +11 -11
  12. data/lib/dm-types/file_path.rb +13 -10
  13. data/lib/dm-types/flag.rb +17 -25
  14. data/lib/dm-types/ip_address.rb +15 -11
  15. data/lib/dm-types/json.rb +17 -14
  16. data/lib/dm-types/paranoid/base.rb +38 -0
  17. data/lib/dm-types/paranoid_boolean.rb +23 -0
  18. data/lib/dm-types/paranoid_datetime.rb +22 -0
  19. data/lib/dm-types/regexp.rb +8 -8
  20. data/lib/dm-types/slug.rb +7 -12
  21. data/lib/dm-types/uri.rb +21 -9
  22. data/lib/dm-types/uuid.rb +18 -11
  23. data/lib/dm-types/yaml.rb +12 -10
  24. data/spec/fixtures/article.rb +0 -2
  25. data/spec/fixtures/bookmark.rb +0 -2
  26. data/spec/fixtures/network_node.rb +0 -2
  27. data/spec/fixtures/person.rb +0 -2
  28. data/spec/fixtures/software_package.rb +0 -2
  29. data/spec/fixtures/ticket.rb +2 -4
  30. data/spec/fixtures/tshirt.rb +3 -5
  31. data/spec/integration/bcrypt_hash_spec.rb +33 -31
  32. data/spec/integration/comma_separated_list_spec.rb +55 -53
  33. data/spec/integration/enum_spec.rb +55 -53
  34. data/spec/integration/file_path_spec.rb +105 -103
  35. data/spec/integration/flag_spec.rb +42 -40
  36. data/spec/integration/ip_address_spec.rb +91 -89
  37. data/spec/integration/json_spec.rb +41 -39
  38. data/spec/integration/slug_spec.rb +36 -34
  39. data/spec/integration/uri_spec.rb +82 -79
  40. data/spec/integration/uuid_spec.rb +63 -61
  41. data/spec/integration/yaml_spec.rb +37 -35
  42. data/spec/spec_helper.rb +7 -36
  43. data/spec/unit/bcrypt_hash_spec.rb +18 -10
  44. data/spec/unit/csv_spec.rb +92 -80
  45. data/spec/unit/enum_spec.rb +27 -42
  46. data/spec/unit/epoch_time_spec.rb +18 -7
  47. data/spec/unit/file_path_spec.rb +15 -10
  48. data/spec/unit/flag_spec.rb +13 -36
  49. data/spec/unit/ip_address_spec.rb +13 -10
  50. data/spec/unit/json_spec.rb +21 -14
  51. data/spec/unit/paranoid_boolean_spec.rb +138 -0
  52. data/spec/unit/paranoid_datetime_spec.rb +143 -0
  53. data/spec/unit/regexp_spec.rb +15 -5
  54. data/spec/unit/uri_spec.rb +13 -9
  55. data/spec/unit/yaml_spec.rb +16 -9
  56. data/tasks/local_gemfile.rake +18 -0
  57. data/tasks/spec.rake +0 -3
  58. metadata +122 -52
@@ -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 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 # class ParanoidDateTime
21
+ end # module Property
22
+ end # module DataMapper
@@ -1,18 +1,18 @@
1
- module DataMapper
2
- module Types
3
- class Regexp < DataMapper::Type
4
- primitive String
1
+ require 'dm-core'
5
2
 
6
- def self.load(value, property)
3
+ module DataMapper
4
+ class Property
5
+ class Regexp < String
6
+ def load(value)
7
7
  ::Regexp.new(value) unless value.nil?
8
8
  end
9
9
 
10
- def self.dump(value, property)
10
+ def dump(value)
11
11
  value.source unless value.nil?
12
12
  end
13
13
 
14
- def self.typecast(value, property)
15
- load(value, property)
14
+ def typecast(value)
15
+ load(value)
16
16
  end
17
17
  end
18
18
  end
@@ -1,20 +1,15 @@
1
+ require 'dm-core'
1
2
  require 'stringex'
2
3
 
3
4
  module DataMapper
4
- module Types
5
- class Slug < DataMapper::Type
6
- primitive String
7
- length 2000
8
-
5
+ class Property
6
+ class Slug < String
9
7
  # Maximum length chosen because URI type is limited to 2000
10
8
  # characters, and a slug is a component of a URI, so it should
11
9
  # not exceed the maximum URI length either.
10
+ length 2000
12
11
 
13
- def self.load(value, property)
14
- value
15
- end
16
-
17
- def self.dump(value, property)
12
+ def dump(value)
18
13
  return if value.nil?
19
14
 
20
15
  if value.respond_to?(:to_str)
@@ -24,9 +19,9 @@ module DataMapper
24
19
  end
25
20
  end
26
21
 
27
- def self.escape(string)
22
+ def escape(string)
28
23
  string.to_url
29
24
  end
30
25
  end # class Slug
31
- end # module Types
26
+ end # class Property
32
27
  end # module DataMapper
@@ -1,25 +1,37 @@
1
1
  require 'addressable/uri'
2
+ require 'dm-core'
2
3
 
3
4
  module DataMapper
4
- module Types
5
- class URI < DataMapper::Type
6
- primitive String
7
- length 2000
5
+ class Property
6
+ class URI < String
7
+ length 2000
8
+
9
+ def custom?
10
+ true
11
+ end
12
+
13
+ def primitive?(value)
14
+ value.kind_of?(Addressable::URI)
15
+ end
16
+
17
+ def valid?(value, negated = false)
18
+ super || primitive?(value) || value.kind_of?(::String)
19
+ end
8
20
 
9
21
  # Maximum length chosen based on recommendation:
10
22
  # http://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-an-url
11
23
 
12
- def self.load(value, property)
24
+ def load(value)
13
25
  Addressable::URI.parse(value)
14
26
  end
15
27
 
16
- def self.dump(value, property)
28
+ def dump(value)
17
29
  value.to_s unless value.nil?
18
30
  end
19
31
 
20
- def self.typecast(value, property)
21
- load(value, property)
32
+ def typecast_to_primitive(value)
33
+ load(value)
22
34
  end
23
35
  end # class URI
24
- end # module Types
36
+ end # class Property
25
37
  end # module DataMapper
@@ -1,7 +1,8 @@
1
+ require 'dm-core'
1
2
  require 'uuidtools' # must be ~>2.0
2
3
 
3
4
  module DataMapper
4
- module Types
5
+ class Property
5
6
  # UUID Type
6
7
  # First run at this, because I need it. A few caveats:
7
8
  # * Only works on postgres, using the built-in native uuid type.
@@ -37,25 +38,31 @@ module DataMapper
37
38
  #
38
39
  # -- benburkert Nov 15, 08
39
40
  #
40
- class UUID < DataMapper::Type
41
- primitive String
42
- length 36
41
+ class UUID < String
42
+ length 36
43
43
 
44
- def self.load(value, property)
45
- UUIDTools::UUID.parse(value) unless value.nil?
44
+ # We need to override this method otherwise typecast_to_primitive won't be called.
45
+ # In the future we will set primitive to UUIDTools::UUID but this can happen only
46
+ # when adapters can handle it
47
+ def primitive?(value)
48
+ value.kind_of?(UUIDTools::UUID)
46
49
  end
47
50
 
48
- def self.dump(value, property)
51
+ def dump(value)
49
52
  value.to_s unless value.nil?
50
53
  end
51
54
 
52
- def self.typecast(value, property)
53
- if value.kind_of?(UUIDTools::UUID)
55
+ def load(value)
56
+ if primitive?(value)
54
57
  value
55
- else
56
- load(value, property)
58
+ elsif !value.nil?
59
+ UUIDTools::UUID.parse(value)
57
60
  end
58
61
  end
62
+
63
+ def typecast_to_primitive(value)
64
+ load(value)
65
+ end
59
66
  end
60
67
  end
61
68
  end
@@ -1,34 +1,36 @@
1
1
  require 'yaml'
2
+ require 'dm-core'
2
3
 
3
4
  module DataMapper
4
- module Types
5
- class Yaml < DataMapper::Type
6
- primitive Text
5
+ class Property
6
+ class Yaml < Text
7
+ def custom?
8
+ true
9
+ end
7
10
 
8
- def self.load(value, property)
11
+ def load(value)
9
12
  if value.nil?
10
13
  nil
11
- elsif value.is_a?(String)
14
+ elsif value.is_a?(::String)
12
15
  ::YAML.load(value)
13
16
  else
14
17
  raise ArgumentError.new("+value+ of a property of YAML type must be nil or a String")
15
18
  end
16
19
  end
17
20
 
18
- def self.dump(value, property)
21
+ def dump(value)
19
22
  if value.nil?
20
23
  nil
21
- elsif value.is_a?(String) && value =~ /^---/
24
+ elsif value.is_a?(::String) && value =~ /^---/
22
25
  value
23
26
  else
24
27
  ::YAML.dump(value)
25
28
  end
26
29
  end
27
30
 
28
- def self.typecast(value, property)
29
- # No typecasting; leave values exactly as they're provided.
31
+ def typecast(value)
30
32
  value
31
33
  end
32
34
  end # class Yaml
33
- end # module Types
35
+ end # class Property
34
36
  end # module DataMapper
@@ -31,8 +31,6 @@ module DataMapper
31
31
  before :valid? do
32
32
  self.slug = self.title
33
33
  end
34
-
35
- auto_migrate!
36
34
  end # Article
37
35
  end
38
36
  end
@@ -19,8 +19,6 @@ module DataMapper
19
19
  property :shared, Boolean
20
20
  property :uri, URI
21
21
  property :tags, Yaml
22
-
23
- auto_migrate!
24
22
  end # Bookmark
25
23
  end
26
24
  end
@@ -32,8 +32,6 @@ module DataMapper
32
32
  def runs_ipv4?
33
33
  self.ip_address.ipv4?
34
34
  end
35
-
36
- auto_migrate!
37
35
  end # NetworkNode
38
36
  end # Fixtures
39
37
  end # Types
@@ -21,8 +21,6 @@ module DataMapper
21
21
  property :interests, CommaSeparatedList
22
22
 
23
23
  property :password, BCryptHash
24
-
25
- auto_migrate!
26
24
  end
27
25
  end
28
26
  end
@@ -29,8 +29,6 @@ module DataMapper
29
29
  property :installed_at, DateTime
30
30
  property :installed_by, String
31
31
  end
32
-
33
- auto_migrate!
34
32
  end # SoftwarePackage
35
33
  end # Fixtures
36
34
  end # Types
@@ -7,7 +7,7 @@ module DataMapper
7
7
  #
8
8
 
9
9
  include DataMapper::Resource
10
- include DataMapper::Validate
10
+ include DataMapper::Validations
11
11
 
12
12
  #
13
13
  # Properties
@@ -16,9 +16,7 @@ module DataMapper
16
16
  property :id, Serial
17
17
  property :title, String, :length => 255
18
18
  property :body, Text
19
- property :status, Enum[:unconfirmed, :confirmed, :assigned, :resolved, :not_applicable]
20
-
21
- auto_migrate!
19
+ property :status, Enum, :flags => [:unconfirmed, :confirmed, :assigned, :resolved, :not_applicable]
22
20
  end # Ticket
23
21
  end
24
22
  end
@@ -16,12 +16,10 @@ module DataMapper
16
16
  property :id, Serial
17
17
  property :writing, String
18
18
  property :has_picture, Boolean, :default => false
19
- property :picture, Enum[:octocat, :fork_you, :git_down]
19
+ property :picture, Enum, :flags => [:octocat, :fork_you, :git_down]
20
20
 
21
- property :color, Enum[:white, :black, :red, :orange, :yellow, :green, :cyan, :blue, :purple]
22
- property :size, Flag[:xs, :small, :medium, :large, :xl, :xxl]
23
-
24
- auto_migrate!
21
+ property :color, Enum, :flags => [:white, :black, :red, :orange, :yellow, :green, :cyan, :blue, :purple]
22
+ property :size, Flag, :flags => [:xs, :small, :medium, :large, :xl, :xxl]
25
23
  end # Shirt
26
24
  end # Fixtures
27
25
  end # Types
@@ -5,37 +5,39 @@ try_spec do
5
5
  require './spec/fixtures/person'
6
6
 
7
7
  describe DataMapper::Types::Fixtures::Person do
8
- before :all do
9
- @resource = DataMapper::Types::Fixtures::Person.create(:password => 'DataMapper R0cks!')
10
- DataMapper::Types::Fixtures::Person.create(:password => 'password1')
11
-
12
- @people = DataMapper::Types::Fixtures::Person.all
13
- @resource.reload
14
- end
15
-
16
- it 'persists the password on initial save' do
17
- @resource.password.should == 'DataMapper R0cks!'
18
- @people.last.password.should == 'password1'
19
- end
20
-
21
- it 'recalculates password hash on attribute update' do
22
- @resource.attribute_set(:password, 'bcryptic obscure')
23
- @resource.save
24
-
25
- @resource.reload
26
- @resource.password.should == 'bcryptic obscure'
27
- @resource.password.should_not == 'DataMapper R0cks!'
28
- end
29
-
30
- it 'does not change password value on reload' do
31
- resource = @people.last
32
- original = resource.password.to_s
33
- resource.reload
34
- resource.password.to_s.should == original
35
- end
36
-
37
- it 'uses cost of BCrypt::Engine::DEFAULT_COST' do
38
- @resource.password.cost.should == BCrypt::Engine::DEFAULT_COST
8
+ supported_by :all do
9
+ before :all do
10
+ @resource = DataMapper::Types::Fixtures::Person.create(:password => 'DataMapper R0cks!')
11
+ DataMapper::Types::Fixtures::Person.create(:password => 'password1')
12
+
13
+ @people = DataMapper::Types::Fixtures::Person.all
14
+ @resource.reload
15
+ end
16
+
17
+ it 'persists the password on initial save' do
18
+ @resource.password.should == 'DataMapper R0cks!'
19
+ @people.last.password.should == 'password1'
20
+ end
21
+
22
+ it 'recalculates password hash on attribute update' do
23
+ @resource.attribute_set(:password, 'bcryptic obscure')
24
+ @resource.save
25
+
26
+ @resource.reload
27
+ @resource.password.should == 'bcryptic obscure'
28
+ @resource.password.should_not == 'DataMapper R0cks!'
29
+ end
30
+
31
+ it 'does not change password value on reload' do
32
+ resource = @people.last
33
+ original = resource.password.to_s
34
+ resource.reload
35
+ resource.password.to_s.should == original
36
+ end
37
+
38
+ it 'uses cost of BCrypt::Engine::DEFAULT_COST' do
39
+ @resource.password.cost.should == BCrypt::Engine::DEFAULT_COST
40
+ end
39
41
  end
40
42
  end
41
43
  end
@@ -5,83 +5,85 @@ try_spec do
5
5
  require './spec/fixtures/person'
6
6
 
7
7
  describe DataMapper::Types::Fixtures::Person do
8
- before :all do
9
- @resource = DataMapper::Types::Fixtures::Person.new(:name => '')
10
- end
11
-
12
- describe 'with no interests information' do
8
+ supported_by :all do
13
9
  before :all do
14
- @resource.interests = nil
10
+ @resource = DataMapper::Types::Fixtures::Person.new(:name => '')
15
11
  end
16
12
 
17
- describe 'when dumped and loaded again' do
13
+ describe 'with no interests information' do
18
14
  before :all do
19
- @resource.save.should be_true
20
- @resource.reload
15
+ @resource.interests = nil
21
16
  end
22
17
 
23
- it 'has no interests' do
24
- @resource.interests.should == nil
25
- end
26
- end
27
- end
18
+ describe 'when dumped and loaded again' do
19
+ before :all do
20
+ @resource.save.should be_true
21
+ @resource.reload
22
+ end
28
23
 
29
- describe 'with no interests information' do
30
- before :all do
31
- @resource.interests = []
24
+ it 'has no interests' do
25
+ @resource.interests.should == nil
26
+ end
27
+ end
32
28
  end
33
29
 
34
- describe 'when dumped and loaded again' do
30
+ describe 'with no interests information' do
35
31
  before :all do
36
- @resource.save.should be_true
37
- @resource.reload
32
+ @resource.interests = []
38
33
  end
39
34
 
40
- it 'has empty interests list' do
41
- @resource.interests.should == []
42
- end
43
- end
44
- end
35
+ describe 'when dumped and loaded again' do
36
+ before :all do
37
+ @resource.save.should be_true
38
+ @resource.reload
39
+ end
45
40
 
46
- describe 'with interests information given as a Hash' do
47
- it 'raises ArgumentError' do
48
- lambda do
49
- @resource.interests = { :hash => 'value' }
50
- @resource.save
51
- end.should raise_error(ArgumentError, /must be a string, an array or nil/)
41
+ it 'has empty interests list' do
42
+ @resource.interests.should == []
43
+ end
44
+ end
52
45
  end
53
- end
54
46
 
55
- describe 'with a few items on the interests list' do
56
- before :all do
57
- @input = 'fire, water, fire, a whole lot of other interesting things, ,,,'
58
- @resource.interests = @input
47
+ describe 'with interests information given as a Hash' do
48
+ it 'raises ArgumentError' do
49
+ lambda do
50
+ @resource.interests = { :hash => 'value' }
51
+ @resource.save
52
+ end.should raise_error(ArgumentError, /must be a string, an array or nil/)
53
+ end
59
54
  end
60
55
 
61
- describe 'when dumped and loaded again' do
56
+ describe 'with a few items on the interests list' do
62
57
  before :all do
63
- @resource.save.should be_true
64
- @resource.reload
58
+ @input = 'fire, water, fire, a whole lot of other interesting things, ,,,'
59
+ @resource.interests = @input
65
60
  end
66
61
 
67
- it 'includes "fire" in interests' do
68
- @resource.interests.should include('fire')
69
- end
62
+ describe 'when dumped and loaded again' do
63
+ before :all do
64
+ @resource.save.should be_true
65
+ @resource.reload
66
+ end
70
67
 
71
- it 'includes "water" in interests' do
72
- @resource.interests.should include('water')
73
- end
68
+ it 'includes "fire" in interests' do
69
+ @resource.interests.should include('fire')
70
+ end
74
71
 
75
- it 'includes "a whole lot of other interesting things" in interests' do
76
- @resource.interests.should include('a whole lot of other interesting things')
77
- end
72
+ it 'includes "water" in interests' do
73
+ @resource.interests.should include('water')
74
+ end
78
75
 
79
- it 'has blank entries removed' do
80
- @resource.interests.any? { |i| i.blank? }.should be_false
81
- end
76
+ it 'includes "a whole lot of other interesting things" in interests' do
77
+ @resource.interests.should include('a whole lot of other interesting things')
78
+ end
79
+
80
+ it 'has blank entries removed' do
81
+ @resource.interests.any? { |i| i.blank? }.should be_false
82
+ end
82
83
 
83
- it 'has duplicates removed' do
84
- @resource.interests.select { |i| i == 'fire' }.size.should == 1
84
+ it 'has duplicates removed' do
85
+ @resource.interests.select { |i| i == 'fire' }.size.should == 1
86
+ end
85
87
  end
86
88
  end
87
89
  end