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.
- 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
|