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,168 @@
|
|
1
|
+
# Approach
|
2
|
+
#
|
3
|
+
# We need to detect whether or not the underlying Hash or Array changed and
|
4
|
+
# update the dirty-ness of the encapsulating Resource accordingly (so that it
|
5
|
+
# will actually save).
|
6
|
+
#
|
7
|
+
# DM's state-tracking code only triggers dirty-ness by comparing the new value
|
8
|
+
# against the instance's Property's current value. WRT mutation, we have to
|
9
|
+
# choose one of the following approaches:
|
10
|
+
#
|
11
|
+
# (1) mutate a copy ("after"), then invoke the Resource assignment and State
|
12
|
+
# tracking
|
13
|
+
#
|
14
|
+
# (2) create a copy ("before"), mutate self ("after"), then invoke the
|
15
|
+
# Resource assignment and State tracking
|
16
|
+
#
|
17
|
+
# (1) seemed simpler at first, but it required additional steps to alias the
|
18
|
+
# original (pre-hooked) methods before overriding them (so they could be invoked
|
19
|
+
# externally, ala self.clone.send("orig_...")), and more importantly it resulted
|
20
|
+
# in any external references keeping their old value (instead of getting the
|
21
|
+
# new), like so:
|
22
|
+
#
|
23
|
+
# copy = instance.json
|
24
|
+
# copy[:some] = :value
|
25
|
+
# instance.json[:some] == :value
|
26
|
+
# => true
|
27
|
+
# copy[:some] == :value
|
28
|
+
# => false # fk!
|
29
|
+
#
|
30
|
+
# In order to do (2) and still have State tracking trigger normally, we need to
|
31
|
+
# ensure the Property has a different value other than self when the State
|
32
|
+
# tracking does the comparison. This equates to setting the Property directly
|
33
|
+
# to the "before" value (a clone and thus a different object/value) before
|
34
|
+
# invoking the Resource Property/attribute assignment.
|
35
|
+
#
|
36
|
+
# The cloning of any value might sound expensive, but it's identical in cost to
|
37
|
+
# what you already had to do: assign a cloned copy in order to trigger
|
38
|
+
# dirty-ness (e.g. ::DataMapper::Property::Json):
|
39
|
+
#
|
40
|
+
# model.json = model.json.merge({:some=>:value})
|
41
|
+
#
|
42
|
+
# Hooking Core Classes
|
43
|
+
#
|
44
|
+
# We want to hook certain methods on Hash and Array to trigger dirty-ness in the
|
45
|
+
# resource. However, because these are core classes, they are individually
|
46
|
+
# mapped to C primitives and thus cannot be hooked through #send/#__send__. We
|
47
|
+
# have to override each method, but we don't want to write a lot of code.
|
48
|
+
#
|
49
|
+
# Minimally Invasive
|
50
|
+
#
|
51
|
+
# We also want to extend behaviour of existing class instances instead of
|
52
|
+
# impersonating/delegating from a proxy class of our own, or overriding a global
|
53
|
+
# class behaviour. This is the most flexible approach and least prone to error,
|
54
|
+
# since it leaves open the option for consumers to proxy or override global
|
55
|
+
# classes, and is less likely to interfere with method_missing/etc shenanigans.
|
56
|
+
#
|
57
|
+
# Nested Object Mutations
|
58
|
+
#
|
59
|
+
# Since we use {Array,Hash}#hash to compare before & after, and #hash accounts
|
60
|
+
# for/traverses nested structures, no "deep" inspection logic is technically
|
61
|
+
# necessary. However, Resource#dirty? only queries a cache of dirtied
|
62
|
+
# attributes, whose own population strategy is to hook assignment (instead of
|
63
|
+
# interrogating properties on demand). So the approach is still limited to
|
64
|
+
# top-level mutators.
|
65
|
+
#
|
66
|
+
# Maybe consider optional "advisory" Property#dirty? method for Resource#dirty?
|
67
|
+
# that custom properties could use for this purpose.
|
68
|
+
#
|
69
|
+
# TODO: add support for detecting mutations in nested objects, but we can't
|
70
|
+
# catch the assignment from here (yet?).
|
71
|
+
# TODO: ensure we covered all indirectly-mutable classes that DM uses underneath
|
72
|
+
# a property type
|
73
|
+
# TODO: figure out how to hook core class methods on RBX (which do use #send)
|
74
|
+
|
75
|
+
module DataMapper
|
76
|
+
class Property
|
77
|
+
module DirtyMinder
|
78
|
+
|
79
|
+
module Hooker
|
80
|
+
MUTATION_METHODS = {
|
81
|
+
::Array => %w(
|
82
|
+
[]= push << shift pop insert unshift delete
|
83
|
+
delete_at replace fill clear
|
84
|
+
slice! reverse! rotate! compact! flatten! uniq!
|
85
|
+
collect! map! sort! sort_by! reject! delete_if!
|
86
|
+
select! shuffle!
|
87
|
+
).select { |meth| ::Array.instance_methods.any? { |m| m.to_s == meth } },
|
88
|
+
|
89
|
+
::Hash => %w(
|
90
|
+
[]= store delete delete_if replace update
|
91
|
+
delete rehash shift clear
|
92
|
+
merge! reject! select!
|
93
|
+
).select { |meth| ::Hash.instance_methods.any? { |m| m.to_s == meth } },
|
94
|
+
}.freeze
|
95
|
+
|
96
|
+
def self.extended(instance)
|
97
|
+
# FIXME: DirtyMinder is currently unsupported on RBX, because unlike
|
98
|
+
# the other supported Rubies, RBX core class (e.g. Array, Hash)
|
99
|
+
# methods use #send(). In other words, the other Rubies don't use
|
100
|
+
# #send() (they map directly to their C functions).
|
101
|
+
#
|
102
|
+
# The current methodology takes advantage of this by using #send() to
|
103
|
+
# forward method invocations we've hooked. Supporting RBX will
|
104
|
+
# require finding another way, possibly for all Rubies. In the
|
105
|
+
# meantime, something is better than nothing.
|
106
|
+
return if defined?(RUBY_ENGINE) and RUBY_ENGINE == 'rbx'
|
107
|
+
|
108
|
+
return unless (type = MUTATION_METHODS.keys.find { |k| instance.is_a?(k) })
|
109
|
+
|
110
|
+
instance.extend const_get("#{type}Hooks")
|
111
|
+
end
|
112
|
+
|
113
|
+
MUTATION_METHODS.each do |klass, methods|
|
114
|
+
methods.each do |meth|
|
115
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
116
|
+
module #{klass}Hooks
|
117
|
+
def #{meth}(*)
|
118
|
+
before = self.clone
|
119
|
+
ret = super
|
120
|
+
after = self
|
121
|
+
|
122
|
+
# If the hashes aren't equivalent then we know the Resource
|
123
|
+
# should be dirty. However because we mutated self, normal
|
124
|
+
# State tracking will never trigger, because it will compare the
|
125
|
+
# new value - self - to the Resource's existing property value -
|
126
|
+
# which is also self.
|
127
|
+
#
|
128
|
+
# The solution is to drop 1 level beneath Resource State
|
129
|
+
# tracking and set the value of the property directly to the
|
130
|
+
# previous value (a different object now, because it's a clone).
|
131
|
+
# Then trigger the State tracking like normal.
|
132
|
+
if before.hash != after.hash
|
133
|
+
@property.set(@resource, before)
|
134
|
+
@resource.attribute_set(@property.name, after)
|
135
|
+
end
|
136
|
+
|
137
|
+
ret
|
138
|
+
end
|
139
|
+
end
|
140
|
+
RUBY
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def track(resource, property)
|
145
|
+
@resource, @property = resource, property
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# Catch any direct assignment (#set), and any Resource#reload (set!).
|
150
|
+
def set!(resource, value)
|
151
|
+
# Do not extend non observed value classes
|
152
|
+
if Hooker::MUTATION_METHODS.keys.detect { |klass| value.is_a?(klass) }
|
153
|
+
hook_value(resource, value) unless value.is_a? Hooker
|
154
|
+
end
|
155
|
+
super
|
156
|
+
end
|
157
|
+
|
158
|
+
private
|
159
|
+
|
160
|
+
def hook_value(resource, value)
|
161
|
+
return if value.is_a? Hooker
|
162
|
+
|
163
|
+
value.extend Hooker
|
164
|
+
value.track(resource, self)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module DataMapper
|
2
|
+
class Property
|
3
|
+
module Flags
|
4
|
+
def self.included(base)
|
5
|
+
base.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
6
|
+
extend DataMapper::Property::Flags::ClassMethods
|
7
|
+
|
8
|
+
accept_options :flags
|
9
|
+
attr_reader :flag_map
|
10
|
+
|
11
|
+
class << self
|
12
|
+
attr_accessor :generated_classes
|
13
|
+
end
|
14
|
+
|
15
|
+
self.generated_classes = {}
|
16
|
+
RUBY
|
17
|
+
end
|
18
|
+
|
19
|
+
def custom?
|
20
|
+
true
|
21
|
+
end
|
22
|
+
|
23
|
+
module ClassMethods
|
24
|
+
# TODO: document
|
25
|
+
# @api public
|
26
|
+
def [](*values)
|
27
|
+
if (klass = generated_classes[values.flatten])
|
28
|
+
klass
|
29
|
+
else
|
30
|
+
klass = ::Class.new(self)
|
31
|
+
klass.flags(values)
|
32
|
+
|
33
|
+
generated_classes[values.flatten] = klass
|
34
|
+
|
35
|
+
klass
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/dm-types/uri.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'addressable/uri'
|
2
|
+
require 'dm-core'
|
3
|
+
|
4
|
+
module DataMapper
|
5
|
+
class Property
|
6
|
+
class URI < String
|
7
|
+
load_as Addressable::URI
|
8
|
+
|
9
|
+
# Maximum length chosen based on recommendation:
|
10
|
+
# http://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-an-url
|
11
|
+
length 2083
|
12
|
+
|
13
|
+
def load(value)
|
14
|
+
uri = Addressable::URI.parse(value)
|
15
|
+
uri.normalize unless uri.nil?
|
16
|
+
end
|
17
|
+
|
18
|
+
def dump(value)
|
19
|
+
value.to_str unless value.nil?
|
20
|
+
end
|
21
|
+
|
22
|
+
def typecast(value)
|
23
|
+
load(value) unless value.nil?
|
24
|
+
end
|
25
|
+
end # class URI
|
26
|
+
end # class Property
|
27
|
+
end # module DataMapper
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'dm-core'
|
2
|
+
require 'uuidtools' # must be ~>2.0
|
3
|
+
|
4
|
+
module DataMapper
|
5
|
+
class Property
|
6
|
+
# UUID Type
|
7
|
+
# First run at this, because I need it. A few caveats:
|
8
|
+
# * Only works on postgres, using the built-in native uuid type.
|
9
|
+
# To make it work in mysql, you'll have to add a typemap entry to
|
10
|
+
# the mysql_adapter. I think. I don't have mysql handy, so I'm
|
11
|
+
# not going to try. For SQLite, this will have to inherit from the
|
12
|
+
# String primitive
|
13
|
+
# * Won't accept a random default, because of the namespace clash
|
14
|
+
# between this and the UUIDtools gem. Also can't set the default
|
15
|
+
# type to UUID() (postgres-contrib's native generator) and
|
16
|
+
# automigrate, because auto_migrate! tries to make it a string "UUID()"
|
17
|
+
# Feel free to enchance this, and delete these caveats when they're fixed.
|
18
|
+
#
|
19
|
+
# -- Rando Sept 25, 08
|
20
|
+
#
|
21
|
+
# Actually, setting the primitive to "UUID" is not neccessary and causes
|
22
|
+
# a segfault when trying to query uuid's from the database. The primitive
|
23
|
+
# should be a class which has been added to the do driver you are using.
|
24
|
+
# Also, it's only neccessary to add a class to the do drivers to use as a
|
25
|
+
# primitive when a value cannot be represented as a string. A uuid can be
|
26
|
+
# represented as a string, so setting the primitive to String ensures that
|
27
|
+
# the value argument is a String containing the uuid in string form.
|
28
|
+
#
|
29
|
+
# <strike>It is still neccessary to add the UUID entry to the type map for
|
30
|
+
# each different adapter with their respective database primitive.</strike>
|
31
|
+
#
|
32
|
+
# The method that generates the SQL schema from the typemap currently
|
33
|
+
# ignores the size attribute from the type map if the primitive type
|
34
|
+
# is String. The causes the generated SQL statement to contain a size for
|
35
|
+
# a UUID column (e.g. id UUID(50)), which causes a syntax error in postgres.
|
36
|
+
# Until this is resolved, you will have to manually change the column type
|
37
|
+
# to UUID in a migration, if you want to use postgres' built in UUID type.
|
38
|
+
#
|
39
|
+
# -- benburkert Nov 15, 08
|
40
|
+
#
|
41
|
+
class UUID < String
|
42
|
+
load_as UUIDTools::UUID
|
43
|
+
|
44
|
+
length 36
|
45
|
+
|
46
|
+
def dump(value)
|
47
|
+
value.to_s unless value.nil?
|
48
|
+
end
|
49
|
+
|
50
|
+
def load(value)
|
51
|
+
if value_loaded?(value)
|
52
|
+
value
|
53
|
+
elsif !value.nil?
|
54
|
+
UUIDTools::UUID.parse(value)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def typecast(value)
|
59
|
+
return if value.nil?
|
60
|
+
load(value)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'safe_yaml'
|
3
|
+
require 'dm-core'
|
4
|
+
require 'dm-types/support/dirty_minder'
|
5
|
+
|
6
|
+
module DataMapper
|
7
|
+
class Property
|
8
|
+
class Yaml < Text
|
9
|
+
load_as ::Object
|
10
|
+
|
11
|
+
def load(value)
|
12
|
+
if value.nil?
|
13
|
+
nil
|
14
|
+
elsif value.is_a?(::String)
|
15
|
+
::YAML.safe_load(value)
|
16
|
+
else
|
17
|
+
raise ArgumentError, '+value+ of a property of YAML type must be nil or a String'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def dump(value)
|
22
|
+
if value.nil?
|
23
|
+
nil
|
24
|
+
elsif value.is_a?(::String) && value =~ /^---/
|
25
|
+
value
|
26
|
+
else
|
27
|
+
::YAML.dump(value)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def typecast(value)
|
32
|
+
value
|
33
|
+
end
|
34
|
+
|
35
|
+
include ::DataMapper::Property::DirtyMinder
|
36
|
+
|
37
|
+
end # class Yaml
|
38
|
+
end # class Property
|
39
|
+
end # module DataMapper
|
data/lib/dm-types.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'dm-core'
|
2
|
+
|
3
|
+
module DataMapper
|
4
|
+
class Property
|
5
|
+
autoload :CommaSeparatedList, 'dm-types/comma_separated_list'
|
6
|
+
autoload :Csv, 'dm-types/csv'
|
7
|
+
autoload :BCryptHash, 'dm-types/bcrypt_hash'
|
8
|
+
autoload :Enum, 'dm-types/enum'
|
9
|
+
autoload :EpochTime, 'dm-types/epoch_time'
|
10
|
+
autoload :FilePath, 'dm-types/file_path'
|
11
|
+
autoload :Flag, 'dm-types/flag'
|
12
|
+
autoload :IPAddress, 'dm-types/ip_address'
|
13
|
+
autoload :Json, 'dm-types/json'
|
14
|
+
autoload :Regexp, 'dm-types/regexp'
|
15
|
+
autoload :ParanoidBoolean, 'dm-types/paranoid_boolean'
|
16
|
+
autoload :ParanoidDateTime, 'dm-types/paranoid_datetime'
|
17
|
+
autoload :Slug, 'dm-types/slug'
|
18
|
+
autoload :UUID, 'dm-types/uuid'
|
19
|
+
autoload :URI, 'dm-types/uri'
|
20
|
+
autoload :Yaml, 'dm-types/yaml'
|
21
|
+
autoload :APIKey, 'dm-types/api_key'
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module TypesFixtures
|
3
|
+
|
4
|
+
class Article
|
5
|
+
#
|
6
|
+
# Behaviors
|
7
|
+
#
|
8
|
+
|
9
|
+
include ::DataMapper::Resource
|
10
|
+
|
11
|
+
#
|
12
|
+
# Properties
|
13
|
+
#
|
14
|
+
|
15
|
+
property :id, Serial
|
16
|
+
|
17
|
+
property :title, String, :length => 255
|
18
|
+
property :body, Text
|
19
|
+
|
20
|
+
property :created_at, DateTime
|
21
|
+
property :updated_at, DateTime
|
22
|
+
property :published_at, DateTime
|
23
|
+
|
24
|
+
property :slug, Slug
|
25
|
+
|
26
|
+
#
|
27
|
+
# Hooks
|
28
|
+
#
|
29
|
+
|
30
|
+
before :valid? do
|
31
|
+
self.slug = self.title
|
32
|
+
end
|
33
|
+
end # Article
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module TypesFixtures
|
3
|
+
|
4
|
+
class Bookmark
|
5
|
+
#
|
6
|
+
# Behaviors
|
7
|
+
#
|
8
|
+
|
9
|
+
include ::DataMapper::Resource
|
10
|
+
|
11
|
+
#
|
12
|
+
# Properties
|
13
|
+
#
|
14
|
+
|
15
|
+
property :id, Serial
|
16
|
+
|
17
|
+
property :title, String, :length => 255
|
18
|
+
property :shared, Boolean
|
19
|
+
property :uri, URI
|
20
|
+
property :tags, Yaml
|
21
|
+
end # Bookmark
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module TypesFixtures
|
3
|
+
|
4
|
+
class NetworkNode
|
5
|
+
#
|
6
|
+
# Behaviors
|
7
|
+
#
|
8
|
+
|
9
|
+
include ::DataMapper::Resource
|
10
|
+
|
11
|
+
#
|
12
|
+
# Properties
|
13
|
+
#
|
14
|
+
|
15
|
+
property :id, Serial
|
16
|
+
property :ip_address, IPAddress
|
17
|
+
property :cidr_subnet_bits, Integer
|
18
|
+
property :node_uuid, UUID
|
19
|
+
|
20
|
+
#
|
21
|
+
# API
|
22
|
+
#
|
23
|
+
|
24
|
+
alias_method :uuid, :node_uuid
|
25
|
+
alias_method :uuid=, :node_uuid=
|
26
|
+
|
27
|
+
def runs_ipv6?
|
28
|
+
self.ip_address.ipv6?
|
29
|
+
end
|
30
|
+
|
31
|
+
def runs_ipv4?
|
32
|
+
self.ip_address.ipv4?
|
33
|
+
end
|
34
|
+
end # NetworkNode
|
35
|
+
end # TypesFixtures
|
36
|
+
end # DataMapper
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module TypesFixtures
|
3
|
+
class Person
|
4
|
+
#
|
5
|
+
# Behaviors
|
6
|
+
#
|
7
|
+
|
8
|
+
include DataMapper::Resource
|
9
|
+
|
10
|
+
#
|
11
|
+
# Properties
|
12
|
+
#
|
13
|
+
|
14
|
+
property :id, Serial
|
15
|
+
property :name, String
|
16
|
+
property :positions, Json
|
17
|
+
property :inventions, Yaml
|
18
|
+
property :birthday, EpochTime
|
19
|
+
|
20
|
+
property :interests, CommaSeparatedList
|
21
|
+
|
22
|
+
property :password, BCryptHash
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module TypesFixtures
|
3
|
+
|
4
|
+
class SoftwarePackage
|
5
|
+
#
|
6
|
+
# Behaviors
|
7
|
+
#
|
8
|
+
|
9
|
+
include ::DataMapper::Resource
|
10
|
+
|
11
|
+
#
|
12
|
+
# Properties
|
13
|
+
#
|
14
|
+
|
15
|
+
property :id, Serial
|
16
|
+
without_auto_validations do
|
17
|
+
property :node_number, Integer, :index => true
|
18
|
+
|
19
|
+
property :source_path, FilePath
|
20
|
+
property :destination_path, FilePath
|
21
|
+
|
22
|
+
property :product, String
|
23
|
+
property :version, String
|
24
|
+
property :released_at, DateTime
|
25
|
+
|
26
|
+
property :security_update, Boolean
|
27
|
+
|
28
|
+
property :installed_at, DateTime
|
29
|
+
property :installed_by, String
|
30
|
+
end
|
31
|
+
end # SoftwarePackage
|
32
|
+
end # TypesFixtures
|
33
|
+
end # DataMapper
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module TypesFixtures
|
3
|
+
class Ticket
|
4
|
+
#
|
5
|
+
# Behaviors
|
6
|
+
#
|
7
|
+
|
8
|
+
include DataMapper::Resource
|
9
|
+
include DataMapper::Validations
|
10
|
+
|
11
|
+
#
|
12
|
+
# Properties
|
13
|
+
#
|
14
|
+
|
15
|
+
property :id, Serial
|
16
|
+
property :title, String, :length => 255
|
17
|
+
property :body, Text
|
18
|
+
property :status, Enum[:unconfirmed, :confirmed, :assigned, :resolved, :not_applicable]
|
19
|
+
end # Ticket
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module TypesFixtures
|
3
|
+
|
4
|
+
class TShirt
|
5
|
+
#
|
6
|
+
# Behaviors
|
7
|
+
#
|
8
|
+
|
9
|
+
include ::DataMapper::Resource
|
10
|
+
|
11
|
+
#
|
12
|
+
# Properties
|
13
|
+
#
|
14
|
+
|
15
|
+
property :id, Serial
|
16
|
+
property :writing, String
|
17
|
+
property :has_picture, Boolean, :default => false
|
18
|
+
property :picture, Enum[:octocat, :fork_you, :git_down]
|
19
|
+
|
20
|
+
property :color, Enum[:white, :black, :red, :orange, :yellow, :green, :cyan, :blue, :purple]
|
21
|
+
property :size, Flag[:xs, :small, :medium, :large, :xl, :xxl], :default => [:xs]
|
22
|
+
end # Shirt
|
23
|
+
end # TypesFixtures
|
24
|
+
end # DataMapper
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
try_spec do
|
4
|
+
require_relative '../fixtures/api_user'
|
5
|
+
|
6
|
+
describe DataMapper::TypesFixtures::APIUser do
|
7
|
+
supported_by :all do
|
8
|
+
subject { described_class.new(:name => 'alice') }
|
9
|
+
|
10
|
+
let(:original_api_key) { subject.api_key }
|
11
|
+
|
12
|
+
it "has a default value" do
|
13
|
+
expect(original_api_key).not_to be_nil
|
14
|
+
end
|
15
|
+
|
16
|
+
it "preserves the default value" do
|
17
|
+
expect(subject.api_key).to eq original_api_key
|
18
|
+
end
|
19
|
+
|
20
|
+
it "generates unique API Keys for each resource" do
|
21
|
+
other_resource = described_class.new(:name => 'eve')
|
22
|
+
|
23
|
+
expect(other_resource.api_key).not_to eq original_api_key
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
try_spec do
|
4
|
+
|
5
|
+
require_relative '../fixtures/person'
|
6
|
+
|
7
|
+
describe DataMapper::TypesFixtures::Person do
|
8
|
+
supported_by :all do
|
9
|
+
before :all do
|
10
|
+
@resource = DataMapper::TypesFixtures::Person.create(:password => 'DataMapper R0cks!')
|
11
|
+
DataMapper::TypesFixtures::Person.create(:password => 'password1')
|
12
|
+
|
13
|
+
@people = DataMapper::TypesFixtures::Person.all
|
14
|
+
@resource.reload
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'persists the password on initial save' do
|
18
|
+
expect(@resource.password).to eq 'DataMapper R0cks!'
|
19
|
+
expect(@people.last.password).to eq '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
|
+
expect(@resource.password).to eq 'bcryptic obscure'
|
28
|
+
expect(@resource.password).not_to eq '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
|
+
expect(resource.password.to_s).to eq original
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'uses cost of BCrypt::Engine::DEFAULT_COST' do
|
39
|
+
expect(@resource.password.cost).to eq BCrypt::Engine::DEFAULT_COST
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'allows Bcrypt::Password#hash to be an Integer' do
|
43
|
+
expect(@resource.password.hash).to be_kind_of(Integer)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|