dm-types 0.10.2 → 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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