reindeer 0.0.1
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.
- data/.rspec +6 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +20 -0
- data/LICENSE +21 -0
- data/README.md +177 -0
- data/Rakefile +9 -0
- data/lib/reindeer.rb +45 -0
- data/lib/reindeer/meta.rb +99 -0
- data/lib/reindeer/meta/attribute.rb +182 -0
- data/lib/reindeer/role.rb +32 -0
- data/lib/reindeer/role/typeconstraint.rb +18 -0
- data/lib/reindeer/version.rb +3 -0
- data/reindeer.gemspec +24 -0
- data/spec/reindeer/attributes_spec.rb +222 -0
- data/spec/reindeer/construction_spec.rb +20 -0
- data/spec/reindeer/roles_spec.rb +79 -0
- data/spec/reindeer/types_spec.rb +53 -0
- data/spec/reindeer_spec.rb +10 -0
- metadata +100 -0
data/.rspec
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
GEM
|
|
2
|
+
remote: http://rubygems.org/
|
|
3
|
+
specs:
|
|
4
|
+
diff-lcs (1.1.3)
|
|
5
|
+
rake (10.0.3)
|
|
6
|
+
rspec (2.11.0)
|
|
7
|
+
rspec-core (~> 2.11.0)
|
|
8
|
+
rspec-expectations (~> 2.11.0)
|
|
9
|
+
rspec-mocks (~> 2.11.0)
|
|
10
|
+
rspec-core (2.11.1)
|
|
11
|
+
rspec-expectations (2.11.3)
|
|
12
|
+
diff-lcs (~> 1.1.3)
|
|
13
|
+
rspec-mocks (2.11.3)
|
|
14
|
+
|
|
15
|
+
PLATFORMS
|
|
16
|
+
ruby
|
|
17
|
+
|
|
18
|
+
DEPENDENCIES
|
|
19
|
+
rake (~> 10)
|
|
20
|
+
rspec (~> 2)
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
(The MIT License)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2013 Dan Brook
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the 'Software'), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# Reindeer - Moose sugar in ruby
|
|
2
|
+
|
|
3
|
+
Takes Ruby's existing OO features and extends them with some sugar
|
|
4
|
+
borrowed from [Moose](http://p3rl.org/Moose).
|
|
5
|
+
|
|
6
|
+
# Installation
|
|
7
|
+
|
|
8
|
+
gem install reindeer
|
|
9
|
+
|
|
10
|
+
# Usage
|
|
11
|
+
|
|
12
|
+
require 'reindeer'
|
|
13
|
+
class Point < Reindeer
|
|
14
|
+
has :x, is: :rw, is_a: Integer
|
|
15
|
+
has :y, is: :rw, is_a: Integer
|
|
16
|
+
end
|
|
17
|
+
class Point3D < Point
|
|
18
|
+
has :z, is: :rw, is_a: Integer
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Features
|
|
22
|
+
|
|
23
|
+
These features are supported to a greater or less extent:
|
|
24
|
+
|
|
25
|
+
## Construction
|
|
26
|
+
|
|
27
|
+
The `build` method can be used where one may have previously used
|
|
28
|
+
`initialize`. It is called after all attributes have been setup so,
|
|
29
|
+
laziness permitting, the object should be in a known state.
|
|
30
|
+
|
|
31
|
+
Another facet of this feature is that each `build` method is called in
|
|
32
|
+
the inheritance chain from most-derived to least.
|
|
33
|
+
|
|
34
|
+
## Attributes
|
|
35
|
+
|
|
36
|
+
Declared with the alternative syntax `has` they provide
|
|
37
|
+
additional functionality while still remaining pure Ruby attributes
|
|
38
|
+
under the hood.
|
|
39
|
+
|
|
40
|
+
Their values can be passed to the `new` constructor in a hash where
|
|
41
|
+
the symbolic keys map to attributes of the same. When a value is
|
|
42
|
+
specified for a `lazy` attribute it obviates the laziness.
|
|
43
|
+
|
|
44
|
+
The following options are supported:
|
|
45
|
+
|
|
46
|
+
### is (aka accessors)
|
|
47
|
+
|
|
48
|
+
Available in 3 flavours:
|
|
49
|
+
|
|
50
|
+
* `:ro`
|
|
51
|
+
* `:rw`
|
|
52
|
+
* `:bare`
|
|
53
|
+
|
|
54
|
+
The first two provide accessors like `attr_reader` and
|
|
55
|
+
`attr_{reader,writer}` combined. The third explicitly provides no
|
|
56
|
+
accessors which can be useful when delegators are specified.
|
|
57
|
+
|
|
58
|
+
The default behaviour is `:ro`.
|
|
59
|
+
|
|
60
|
+
### required (aka required attributes)
|
|
61
|
+
|
|
62
|
+
If specified with a `true` value then the attribute must be specified
|
|
63
|
+
at build time. Additionally `required` attributes can't be `lazy`
|
|
64
|
+
attributes.
|
|
65
|
+
|
|
66
|
+
### default (aka default attribute values)
|
|
67
|
+
|
|
68
|
+
Can take either a value or something callable (e.g a `Proc`). If a
|
|
69
|
+
value is provided it is `clone`d and if a callable is provided it is
|
|
70
|
+
`call`ed without any arguments. The resulting value is used to
|
|
71
|
+
populate the attribute if it wasn't provided to the constructor at
|
|
72
|
+
object construction time or on first access if the attribute is
|
|
73
|
+
`lazy`.
|
|
74
|
+
|
|
75
|
+
### lazy (aka lazily evaluated)
|
|
76
|
+
|
|
77
|
+
Expects a `Boolean` and if `true` then the attribute's value isn't
|
|
78
|
+
generated until it is accessed (if at all). If specified the attribute
|
|
79
|
+
*must* also either have a `builder` or `default` specified otherwise an
|
|
80
|
+
`Reindeer::Meta::Attribute::AttributeError` is thrown.
|
|
81
|
+
|
|
82
|
+
### lazy_build
|
|
83
|
+
|
|
84
|
+
If passed `true` makes the attribute `lazy` and expects a private
|
|
85
|
+
`builder` method of the same name as the attribute, but prefixed with
|
|
86
|
+
`build_`, to be defined e.g given `has :foo, lazy_build: true` the
|
|
87
|
+
private instance method `build_foo` should be defined. In addition
|
|
88
|
+
clearer and predicate methods will be installed with the prefixes
|
|
89
|
+
`clear_` and `has_` respectively e.g `clear_foo!` and `has_foo?`.
|
|
90
|
+
|
|
91
|
+
### handles (aka delegation methods)
|
|
92
|
+
|
|
93
|
+
Given an array of symbols each one adds an instance method that
|
|
94
|
+
delegates to a method of the same name on the attribute value.
|
|
95
|
+
|
|
96
|
+
### type_of (aka type constraints)
|
|
97
|
+
|
|
98
|
+
Expects a class that composes the `Reindeer::Role::TypeConstraint`
|
|
99
|
+
role. At the point a value is about set against an attribute it is
|
|
100
|
+
checked against the type constraint, if valid then the value is set if
|
|
101
|
+
not then an `Reindeer::TypeConstraint::Invalid` exception is raised.
|
|
102
|
+
|
|
103
|
+
## Roles
|
|
104
|
+
|
|
105
|
+
These are implemented in terms of `Module` and act to serve a similar
|
|
106
|
+
purpose. What they provide in addition to `Module` are required
|
|
107
|
+
methods and the attributes described above.
|
|
108
|
+
|
|
109
|
+
To compose a role in a Reindeer class two expressions are required,
|
|
110
|
+
`with` and `meta.compose!`, the former behaves like `include` and the
|
|
111
|
+
latter brings in the role attributes and asserts the existence of any
|
|
112
|
+
required methods e.g
|
|
113
|
+
|
|
114
|
+
module Breakable
|
|
115
|
+
include Reindeer::Role
|
|
116
|
+
has :is_broken, default: -> { false }
|
|
117
|
+
requires :fix!
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
class Egg < Reindeer
|
|
121
|
+
with Breakable
|
|
122
|
+
|
|
123
|
+
def fix!
|
|
124
|
+
throw :no_dice if is_broken
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
meta.compose!
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
The `.does?` method can be used to inspect which roles have been
|
|
131
|
+
consumed e.g `Egg.does?(Breakable) == true`.
|
|
132
|
+
|
|
133
|
+
For further elaboration on the subject of roles see the
|
|
134
|
+
[Moose::Manual::Roles](https://metacpan.org/module/Moose::Manual::Roles)
|
|
135
|
+
documentation.
|
|
136
|
+
|
|
137
|
+
## Class constraints and Type constraints
|
|
138
|
+
|
|
139
|
+
Given that Ruby has a well established class system one need only
|
|
140
|
+
assert an attribute is of a given (existing) class a Reindeer will go
|
|
141
|
+
to the trouble of asserting that when the attribute value is set e.g
|
|
142
|
+
|
|
143
|
+
class AccountSqlTable < Reindeer
|
|
144
|
+
has :id, is_a: Fixnum
|
|
145
|
+
has :owner, is_a: String
|
|
146
|
+
has :amount, is_a: Float
|
|
147
|
+
# ...
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
However if you need a specific type of class (e.g strings of a certain
|
|
151
|
+
length) then a custom type constraint is needed. These can be defined
|
|
152
|
+
simply by composing the `Reindeer::Role::TypeConstraint` and
|
|
153
|
+
implementing a `verify` method e.g
|
|
154
|
+
|
|
155
|
+
class Varchar255 < Reindeer
|
|
156
|
+
with Reindeer::Role::TypeConstraint
|
|
157
|
+
def verify(v)
|
|
158
|
+
v.length <= 255
|
|
159
|
+
end
|
|
160
|
+
meta.compose!
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
class AccountSqlTable # continued from above
|
|
164
|
+
has :summary, type_of: Varchar255
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
*NB* The distinction between class and type constraints seems apt at this
|
|
168
|
+
point but is by no means set in stone. Hopefully the passage of time
|
|
169
|
+
shall enlighten us on the matter.
|
|
170
|
+
|
|
171
|
+
# Contributing
|
|
172
|
+
|
|
173
|
+
Pull requests welcome.
|
|
174
|
+
|
|
175
|
+
# Author
|
|
176
|
+
|
|
177
|
+
Dan Brook `<dan@broquaint.com>`
|
data/Rakefile
ADDED
data/lib/reindeer.rb
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
require 'reindeer/meta'
|
|
2
|
+
require 'reindeer/role'
|
|
3
|
+
require 'reindeer/role/typeconstraint'
|
|
4
|
+
|
|
5
|
+
class Reindeer
|
|
6
|
+
class << self
|
|
7
|
+
# XXX Support a single name for now i.e has %i[a b] won't work yet.
|
|
8
|
+
def has(name, opts={})
|
|
9
|
+
# XXX Should really do this at this once everything has been
|
|
10
|
+
# defined not up front like this. Don't know of any hooks though :(
|
|
11
|
+
meta.add_attribute(name, opts).install_methods_in(self)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def with(role)
|
|
15
|
+
meta.add_role(role)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def does?(role)
|
|
19
|
+
meta.all_roles.include? role
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def inherited(subclass)
|
|
23
|
+
provide_meta subclass
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def provide_meta(subclass)
|
|
27
|
+
meta = Reindeer::Meta.new(subclass)
|
|
28
|
+
meth = Proc.new { meta }
|
|
29
|
+
klass = class << subclass; self; end
|
|
30
|
+
klass.__send__ :define_method, :meta, meth
|
|
31
|
+
subclass.__send__ :define_method, :meta, meth
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def initialize(args={})
|
|
36
|
+
meta.setup_attributes(self, args)
|
|
37
|
+
meta.build_all(self, args)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def build(args); end
|
|
41
|
+
|
|
42
|
+
def does?(role)
|
|
43
|
+
meta.all_roles.include? role
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
require 'reindeer/meta/attribute'
|
|
2
|
+
|
|
3
|
+
class Reindeer
|
|
4
|
+
class Meta
|
|
5
|
+
|
|
6
|
+
attr_reader :klass
|
|
7
|
+
attr_reader :required_methods
|
|
8
|
+
|
|
9
|
+
def initialize(klass)
|
|
10
|
+
@klass = klass # Hrm, circular? Not a problem with constants?
|
|
11
|
+
# TODO Use a hash
|
|
12
|
+
@attributes = []
|
|
13
|
+
@roles = []
|
|
14
|
+
@required_methods = []
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def build_all(obj, args)
|
|
18
|
+
to_build = obj.class.ancestors.take_while { |klass|
|
|
19
|
+
klass != Reindeer
|
|
20
|
+
}.reverse
|
|
21
|
+
|
|
22
|
+
(to_build - obj.class.included_modules).each { |klass|
|
|
23
|
+
# TODO assume build is private.
|
|
24
|
+
build = klass.instance_method(:build)
|
|
25
|
+
build.bind(obj).call(args)
|
|
26
|
+
}
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def compose!
|
|
30
|
+
all_roles.each do |role|
|
|
31
|
+
role.assert_requires klass
|
|
32
|
+
# role.compose_methods! klass
|
|
33
|
+
klass.__send__ :include, role # Blech
|
|
34
|
+
role.role_meta.get_attributes.each do |attr|
|
|
35
|
+
attr.install_methods_in klass
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
get_attributes.push(*role.role_meta.get_attributes)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def add_role(role)
|
|
43
|
+
@roles << role
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def all_roles
|
|
47
|
+
@roles
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Not sure if this is the best place for it.
|
|
51
|
+
def add_required_method(method)
|
|
52
|
+
@required_methods << method
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def get_attributes
|
|
56
|
+
@attributes
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def get_all_attributes
|
|
60
|
+
all_classes = klass.ancestors.take_while{|k| k!=Reindeer}.select{|c|
|
|
61
|
+
c.class == Class
|
|
62
|
+
}.reverse
|
|
63
|
+
all_classes.collect{|c| c.meta.get_attributes}.flatten
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def add_attribute(name, opts)
|
|
67
|
+
attr = Reindeer::Meta::Attribute.new(name, opts)
|
|
68
|
+
get_attributes << attr
|
|
69
|
+
return attr
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def setup_attributes(obj, args)
|
|
73
|
+
for attr in get_all_attributes
|
|
74
|
+
name = attr.name
|
|
75
|
+
if attr.required? and not args.has_key? name
|
|
76
|
+
raise Meta::Attribute::AttributeError,
|
|
77
|
+
"Did not specify required argument '#{name}'"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
obj.instance_eval do
|
|
81
|
+
if args.has_key?(name)
|
|
82
|
+
attr.set_value_for self, args[name]
|
|
83
|
+
elsif attr.has_default? and not attr.is_lazy?
|
|
84
|
+
attr.set_value_for self, attr.get_default_value
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def has_attribute?(name)
|
|
91
|
+
get_attributes.any? {|a| a.name == name }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def get_attribute(sym)
|
|
95
|
+
sym = sym.sub(/^@/, '').to_sym if sym.is_a?(String)
|
|
96
|
+
get_attributes.select{|a| a.name == sym}.first
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
class Reindeer
|
|
2
|
+
class Meta
|
|
3
|
+
class Attribute
|
|
4
|
+
# Exceptions!
|
|
5
|
+
class AttributeError < StandardError; end
|
|
6
|
+
|
|
7
|
+
attr_reader :name
|
|
8
|
+
|
|
9
|
+
attr_reader :is_ro, :is_rw, :is_bare
|
|
10
|
+
|
|
11
|
+
attr_reader :is_a, :type_of
|
|
12
|
+
|
|
13
|
+
attr_reader :default_value
|
|
14
|
+
attr_reader :lazy_builder, :lazy_build
|
|
15
|
+
|
|
16
|
+
attr_reader :handles
|
|
17
|
+
|
|
18
|
+
def initialize(name, opts)
|
|
19
|
+
@name = name
|
|
20
|
+
process_opts opts
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def to_var
|
|
24
|
+
"@#{name.to_s}"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def install_methods_in(klass)
|
|
28
|
+
install_accessors_in klass
|
|
29
|
+
if lazy_build
|
|
30
|
+
install_clearer_in klass
|
|
31
|
+
install_predicate_in klass
|
|
32
|
+
end
|
|
33
|
+
install_delegators klass if has_handles?
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def install_accessors_in(klass)
|
|
37
|
+
return if is_bare
|
|
38
|
+
|
|
39
|
+
if is_lazy?
|
|
40
|
+
attr_name = to_var
|
|
41
|
+
builder = lazy_builder
|
|
42
|
+
klass.__send__ :define_method, name, Proc.new {
|
|
43
|
+
if instance_variable_defined? attr_name
|
|
44
|
+
instance_variable_get attr_name
|
|
45
|
+
else
|
|
46
|
+
meta.get_attribute(attr_name).set_value_for(
|
|
47
|
+
self,
|
|
48
|
+
builder.is_a?(Symbol) ? __send__(builder) : builder.call()
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
}
|
|
52
|
+
else
|
|
53
|
+
name_sym = name
|
|
54
|
+
klass.class_eval { self.__send__ :attr_reader, name_sym }
|
|
55
|
+
if is_rw
|
|
56
|
+
klass.__send__ :define_method, "#{name}=", Proc.new {|v|
|
|
57
|
+
meta.get_attribute(name_sym).set_value_for(self, v)
|
|
58
|
+
}
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def install_clearer_in(klass)
|
|
64
|
+
attr_name = to_var
|
|
65
|
+
klass.__send__ :define_method, "clear_#{name}!", Proc.new {
|
|
66
|
+
remove_instance_variable attr_name
|
|
67
|
+
}
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def install_predicate_in(klass)
|
|
71
|
+
attr_name = to_var
|
|
72
|
+
klass.__send__ :define_method, "has_#{name}?", Proc.new {
|
|
73
|
+
instance_variable_defined? attr_name
|
|
74
|
+
}
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def install_delegators(klass)
|
|
78
|
+
attr_name = to_var
|
|
79
|
+
handles.each do |method|
|
|
80
|
+
klass.__send__ :define_method, method, Proc.new {|*args|
|
|
81
|
+
instance_variable_get(attr_name).__send__(method, *args)
|
|
82
|
+
}
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def set_value_for(instance, value)
|
|
87
|
+
if is_a and not value.is_a? is_a
|
|
88
|
+
raise Meta::Attribute::AttributeError,
|
|
89
|
+
"The value for '#{name}' of type '#{value.class}' is not a '#{is_a}'"
|
|
90
|
+
end
|
|
91
|
+
type_of.check_constraint(value) if type_of
|
|
92
|
+
instance.instance_variable_set to_var, value
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def get_default_value
|
|
96
|
+
default_value.call
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Predicates
|
|
100
|
+
def required?
|
|
101
|
+
@required
|
|
102
|
+
end
|
|
103
|
+
# There must be a cleaner way to get a boolean.
|
|
104
|
+
def has_default?
|
|
105
|
+
not default_value.nil?
|
|
106
|
+
end
|
|
107
|
+
def is_lazy?
|
|
108
|
+
not @lazy_builder.nil?
|
|
109
|
+
end
|
|
110
|
+
def has_handles?
|
|
111
|
+
not handles.nil?
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
private
|
|
115
|
+
|
|
116
|
+
def process_opts(opts)
|
|
117
|
+
process_is opts[:is]
|
|
118
|
+
|
|
119
|
+
@required = opts[:required]
|
|
120
|
+
@is_a = opts[:is_a] if opts.has_key?(:is_a)
|
|
121
|
+
@type_of = process_type_of opts[:type_of] if opts.has_key?(:type_of)
|
|
122
|
+
@default_value = process_default opts[:default] if opts.has_key?(:default)
|
|
123
|
+
|
|
124
|
+
process_lazy opts[:lazy], opts if opts.has_key?(:lazy)
|
|
125
|
+
|
|
126
|
+
if opts[:lazy_build]
|
|
127
|
+
raise AttributeError, "Can't have lazy_build and default, pick one!" if has_default?
|
|
128
|
+
@lazy_builder = "build_#{name}".to_sym
|
|
129
|
+
@lazy_build = true
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
raise AttributeError, "Can't be lazy and required, pick one!" if required? and is_lazy?
|
|
133
|
+
process_handles opts[:handles] if opts.has_key?(:handles)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def process_is(val)
|
|
137
|
+
case val
|
|
138
|
+
when nil then @is_ro = true # Default behaviour if 'is' isn't specified.
|
|
139
|
+
when :ro then @is_ro = true
|
|
140
|
+
when :rw then @is_rw = true
|
|
141
|
+
when :bare then @is_bare = true
|
|
142
|
+
else raise AttributeError, "Unknown value for is '#{val}'"
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def process_type_of(type_constraint)
|
|
147
|
+
tc = type_constraint.new # XXX Bleh!
|
|
148
|
+
if tc.respond_to? :verify # TODO Implement .does?
|
|
149
|
+
tc
|
|
150
|
+
elsif type_constraint.class == Class # or something
|
|
151
|
+
Reindeer::TypeConstraint::Class.new(isa)
|
|
152
|
+
else
|
|
153
|
+
raise AttributeError, "Unknown type constraint '#{isa}' for #{name}"
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# TODO check default is callable.
|
|
158
|
+
def process_default(default)
|
|
159
|
+
if default.is_a?(Proc)
|
|
160
|
+
default
|
|
161
|
+
else
|
|
162
|
+
Proc.new { default.clone }
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def process_lazy(is_lazy, opts)
|
|
167
|
+
if opts[:builder] and opts[:default]
|
|
168
|
+
raise AttributeError, "Can't use lazy & builder for lazy"
|
|
169
|
+
elsif not opts[:builder] and not opts[:default]
|
|
170
|
+
raise AttributeError, "Must specify lazy or builder for lazy"
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
@lazy_builder = opts[:builder] || process_default(opts[:default])
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def process_handles(handles)
|
|
177
|
+
raise AttributeError, "Only support an Array of methods is supported for handles" unless handles.is_a?(Array)
|
|
178
|
+
@handles = handles
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
class Reindeer
|
|
2
|
+
module Role
|
|
3
|
+
class RoleError < StandardError; end #ahem
|
|
4
|
+
|
|
5
|
+
def Role.included(mod)
|
|
6
|
+
mod.module_eval {
|
|
7
|
+
class << self;
|
|
8
|
+
meta = Reindeer::Meta.new(self)
|
|
9
|
+
define_method :role_meta, Proc.new { meta }
|
|
10
|
+
|
|
11
|
+
# Make this more composable?
|
|
12
|
+
define_method :has, Proc.new { |name, opts={}|
|
|
13
|
+
role_meta.add_attribute(name, opts)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
define_method :requires, Proc.new { |method|
|
|
17
|
+
role_meta.add_required_method(method)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
define_method :assert_requires, Proc.new { |klass|
|
|
21
|
+
not_defined = role_meta.required_methods.select do |meth|
|
|
22
|
+
not klass.instance_methods(false).include?(meth)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
return if not_defined.empty?
|
|
26
|
+
raise RoleError, "The class '#{klass}' composed '#{self}' but didn't define #{not_defined.join ', '}"
|
|
27
|
+
}
|
|
28
|
+
end
|
|
29
|
+
}
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Reindeer::Role::TypeConstraint
|
|
2
|
+
include Reindeer::Role
|
|
3
|
+
requires :verify
|
|
4
|
+
|
|
5
|
+
def check_constraint(v)
|
|
6
|
+
raise Reindeer::TypeConstraint::Invalid, error_message_for(v) unless verify(v)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def error_message_for(v)
|
|
10
|
+
return "The value '%s' not considered valid by %s" % [v, self.class]
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class Reindeer
|
|
15
|
+
class TypeConstraint
|
|
16
|
+
class Invalid < StandardError; end
|
|
17
|
+
end
|
|
18
|
+
end
|
data/reindeer.gemspec
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
lib = File.expand_path('../lib/', __FILE__)
|
|
2
|
+
$:.unshift lib unless $:.include?(lib)
|
|
3
|
+
|
|
4
|
+
require "reindeer/version"
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |s|
|
|
7
|
+
s.name = 'reindeer'
|
|
8
|
+
s.version = Reindeer::VERSION
|
|
9
|
+
|
|
10
|
+
s.required_ruby_version = ">= 1.9"
|
|
11
|
+
|
|
12
|
+
s.summary = "Moose sugar in Ruby"
|
|
13
|
+
s.description = "Adds featureful attributes, roles and type constraints"
|
|
14
|
+
|
|
15
|
+
s.authors = ["Dan Brook"]
|
|
16
|
+
s.email = 'dan@broquaint.com'
|
|
17
|
+
s.homepage = 'http://github.com/broquaint/reindeer'
|
|
18
|
+
|
|
19
|
+
s.files = `git ls-files`.split("\n") - %w(.rvmrc .gitignore)
|
|
20
|
+
s.test_files = `git ls-files spec`.split("\n")
|
|
21
|
+
|
|
22
|
+
s.add_development_dependency 'rake', '~> 0.9.2'
|
|
23
|
+
s.add_development_dependency 'rspec', '~> 2'
|
|
24
|
+
end
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
require 'reindeer'
|
|
2
|
+
|
|
3
|
+
describe 'Reindeer attributes' do
|
|
4
|
+
it 'should setup a method and initialize an attribute' do
|
|
5
|
+
class FirstOne < Reindeer
|
|
6
|
+
has :abc
|
|
7
|
+
end
|
|
8
|
+
obj = FirstOne.new abc: 'Hello!'
|
|
9
|
+
expect(obj.respond_to? :abc).to be_true
|
|
10
|
+
expect(obj.abc).to eq('Hello!')
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it 'should setup two methods and initialize one attribute' do
|
|
14
|
+
class SecondOne < Reindeer
|
|
15
|
+
has :foo
|
|
16
|
+
has :bar
|
|
17
|
+
end
|
|
18
|
+
obj = SecondOne.new foo: 'World!'
|
|
19
|
+
expect(obj.respond_to? :foo).to be_true
|
|
20
|
+
expect(obj.respond_to? :bar).to be_true
|
|
21
|
+
expect(obj.foo).to eq('World!')
|
|
22
|
+
expect(obj.bar).to be_nil
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it 'should honour is option' do
|
|
26
|
+
class ThirdOne < Reindeer
|
|
27
|
+
has :baz, is: :ro
|
|
28
|
+
has :quux, is: :rw
|
|
29
|
+
has :xuuq, is: :bare
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
obj = ThirdOne.new baz: 'Sawasdee!'
|
|
33
|
+
expect(obj.respond_to? :baz).to be_true
|
|
34
|
+
expect(obj.respond_to? :baz=).to be_false
|
|
35
|
+
expect(obj.respond_to? :quux).to be_true
|
|
36
|
+
expect(obj.respond_to? :quux=).to be_true
|
|
37
|
+
expect(obj.respond_to? :xuuq).to be_false
|
|
38
|
+
expect(obj.meta.has_attribute?(:xuuq)).to be_true
|
|
39
|
+
expect(obj.baz).to eq('Sawasdee!')
|
|
40
|
+
expect(obj.quux).to be_nil
|
|
41
|
+
obj.quux = 'super'
|
|
42
|
+
expect(obj.quux).to eq('super')
|
|
43
|
+
|
|
44
|
+
expect {
|
|
45
|
+
class FirstFail < Reindeer
|
|
46
|
+
has :epic, is: 'fail'
|
|
47
|
+
end
|
|
48
|
+
}.to raise_error(Reindeer::Meta::Attribute::AttributeError)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it 'should have required attributes' do
|
|
52
|
+
class SixthOne < Reindeer
|
|
53
|
+
has :foo, required: true
|
|
54
|
+
end
|
|
55
|
+
expect(SixthOne.new(foo: 'yep').foo).to eq('yep')
|
|
56
|
+
expect {
|
|
57
|
+
SixthOne.new
|
|
58
|
+
}.to raise_error(Reindeer::Meta::Attribute::AttributeError)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it 'should have default attribute values' do
|
|
62
|
+
class SeventhOne < Reindeer
|
|
63
|
+
# clone, clone, execute
|
|
64
|
+
has :ichi, default: 'one'
|
|
65
|
+
has :ni, default: %w[two three]
|
|
66
|
+
has :san, default: -> { [:four, :five] }
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
expect(SeventhOne.new.ichi).to eq('one')
|
|
70
|
+
|
|
71
|
+
a = SeventhOne.new.ni
|
|
72
|
+
expect(a).to eq(%w{two three})
|
|
73
|
+
a << 'four five'
|
|
74
|
+
expect(SeventhOne.new.ni).to eq(%w{two three})
|
|
75
|
+
|
|
76
|
+
b = SeventhOne.new.san
|
|
77
|
+
expect(b).to eq([:four, :five])
|
|
78
|
+
b << :six
|
|
79
|
+
expect(SeventhOne.new.san).to eq([:four, :five])
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it 'should have lazily built attribute values' do
|
|
83
|
+
class EighthOne < Reindeer
|
|
84
|
+
private
|
|
85
|
+
def build_ha
|
|
86
|
+
'mmm, lazy'
|
|
87
|
+
end
|
|
88
|
+
public
|
|
89
|
+
has :ha, lazy: true, builder: :build_ha
|
|
90
|
+
has :hok, lazy: true, default: -> { 'not eager' }
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
expect(EighthOne.new.ha).to eq('mmm, lazy')
|
|
94
|
+
expect(EighthOne.new.hok).to eq('not eager')
|
|
95
|
+
|
|
96
|
+
expect {
|
|
97
|
+
class SecondFail < Reindeer
|
|
98
|
+
has :blam, lazy: true, builder: :flub, default: 'blub'
|
|
99
|
+
end
|
|
100
|
+
}.to raise_error(Reindeer::Meta::Attribute::AttributeError)
|
|
101
|
+
expect {
|
|
102
|
+
class ThirdFail < Reindeer
|
|
103
|
+
has :blam, lazy: true
|
|
104
|
+
end
|
|
105
|
+
}.to raise_error(Reindeer::Meta::Attribute::AttributeError)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
it 'should support the lazy_build shorthand' do
|
|
109
|
+
class NinthOne < Reindeer
|
|
110
|
+
has :jet, lazy_build: true
|
|
111
|
+
private
|
|
112
|
+
def build_jet
|
|
113
|
+
:symbolic
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
expect(NinthOne.new.jet).to eq(:symbolic)
|
|
118
|
+
|
|
119
|
+
obj = NinthOne.new
|
|
120
|
+
|
|
121
|
+
expect(obj.has_jet?).to be_false
|
|
122
|
+
expect(obj.jet).to eq(:symbolic)
|
|
123
|
+
expect(obj.has_jet?).to be_true
|
|
124
|
+
obj.clear_jet!
|
|
125
|
+
expect(obj.has_jet?).to be_false
|
|
126
|
+
|
|
127
|
+
expect {
|
|
128
|
+
class FourthFail < Reindeer
|
|
129
|
+
has :nope, lazy_build: true, default: -> { 'boom!' }
|
|
130
|
+
end
|
|
131
|
+
}.to raise_error(Reindeer::Meta::Attribute::AttributeError)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
it 'should support delegation with handles' do
|
|
135
|
+
class TenthOne < Reindeer
|
|
136
|
+
has :bat, is: :bare, handles: [:sub]
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
obj = TenthOne.new(bat: 'zoo')
|
|
140
|
+
expect(obj.sub /z/, 'f').to eq('foo')
|
|
141
|
+
expect(obj.respond_to?(:bat)).to be_false
|
|
142
|
+
|
|
143
|
+
expect {
|
|
144
|
+
class FifthFail < Reindeer
|
|
145
|
+
has :bleh, handles: Object.new
|
|
146
|
+
end
|
|
147
|
+
}.to raise_error(Reindeer::Meta::Attribute::AttributeError)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
it 'should have simple type constraints' do
|
|
151
|
+
class EleventhOne < Reindeer
|
|
152
|
+
has :gau, is_a: String
|
|
153
|
+
has :sip, is_a: Fixnum
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
obj = EleventhOne.new(gau: 'foo', sip: 123)
|
|
157
|
+
expect(obj.gau).to eq('foo')
|
|
158
|
+
expect(obj.sip).to eq(123)
|
|
159
|
+
|
|
160
|
+
expect {
|
|
161
|
+
EleventhOne.new(gau: [])
|
|
162
|
+
}.to raise_error(Reindeer::Meta::Attribute::AttributeError)
|
|
163
|
+
expect {
|
|
164
|
+
EleventhOne.new(sip: {})
|
|
165
|
+
}.to raise_error(Reindeer::Meta::Attribute::AttributeError)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
it 'should should consistently apply type constraints' do
|
|
169
|
+
class TwelvethOne < Reindeer
|
|
170
|
+
has :une, is_a: Array, lazy_build: true
|
|
171
|
+
has :deux, is_a: Hash, is: :rw
|
|
172
|
+
private
|
|
173
|
+
def build_une
|
|
174
|
+
%w{cool beans}
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
obj = TwelvethOne.new
|
|
179
|
+
expect(obj.une).to eq(%w{cool beans})
|
|
180
|
+
obj.deux = { hashie: 'hash' }
|
|
181
|
+
expect(obj.deux).to eq({ hashie: 'hash' })
|
|
182
|
+
|
|
183
|
+
expect {
|
|
184
|
+
TwelvethOne.new.deux = []
|
|
185
|
+
}.to raise_error(Reindeer::Meta::Attribute::AttributeError)
|
|
186
|
+
|
|
187
|
+
expect {
|
|
188
|
+
class SixthFail < Reindeer
|
|
189
|
+
has :saywaht, is_a: Regexp, lazy: true, default: 'this here'
|
|
190
|
+
end
|
|
191
|
+
SixthFail.new.saywaht
|
|
192
|
+
}.to raise_error(Reindeer::Meta::Attribute::AttributeError)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
it 'should compose attributes up the inheritance chain' do
|
|
196
|
+
class SeventeenthOne < Reindeer
|
|
197
|
+
has :foo
|
|
198
|
+
end
|
|
199
|
+
module FourthRole
|
|
200
|
+
include Reindeer::Role
|
|
201
|
+
has :bar
|
|
202
|
+
end
|
|
203
|
+
class EighteenthOne < SeventeenthOne
|
|
204
|
+
with FourthRole
|
|
205
|
+
has :baz
|
|
206
|
+
meta.compose!
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
obj = EighteenthOne.new(foo: 1, bar: 2, baz: 3)
|
|
210
|
+
expect(obj.foo).to eq(1)
|
|
211
|
+
expect(obj.bar).to eq(2)
|
|
212
|
+
expect(obj.baz).to eq(3)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
it 'should raise an exception for lazy required attributes' do
|
|
216
|
+
expect {
|
|
217
|
+
class LazyRequiredFail < Reindeer
|
|
218
|
+
has :zoiks, lazy: true, required: true
|
|
219
|
+
end
|
|
220
|
+
}.to raise_error(Reindeer::Meta::Attribute::AttributeError)
|
|
221
|
+
end
|
|
222
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require 'reindeer'
|
|
2
|
+
|
|
3
|
+
describe 'Reindeer construction' do
|
|
4
|
+
it 'should call all build methods' do
|
|
5
|
+
class FifteenthOne < Reindeer
|
|
6
|
+
def build(args)
|
|
7
|
+
things << :super
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
class SixteenthOne < FifteenthOne
|
|
11
|
+
has :things, default: []
|
|
12
|
+
def build(args)
|
|
13
|
+
things << :neat
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
obj = SixteenthOne.new
|
|
18
|
+
expect(obj.things).to eq([:super, :neat])
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
require 'reindeer'
|
|
2
|
+
|
|
3
|
+
describe 'Reindeer roles' do
|
|
4
|
+
it 'should compose a role' do
|
|
5
|
+
# TODO more tests smaller roles (to begin with)
|
|
6
|
+
module FirstRole
|
|
7
|
+
include Reindeer::Role
|
|
8
|
+
has :trois, default: 'cool'
|
|
9
|
+
requires :quatre
|
|
10
|
+
def cinq
|
|
11
|
+
%w{cool beans}
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
class ThirteenthOne < Reindeer
|
|
15
|
+
with FirstRole
|
|
16
|
+
def quatre
|
|
17
|
+
'beans'
|
|
18
|
+
end
|
|
19
|
+
meta.compose!
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
obj = ThirteenthOne.new
|
|
23
|
+
expect(obj.trois).to eq('cool')
|
|
24
|
+
expect(obj.quatre).to eq('beans')
|
|
25
|
+
expect(obj.cinq).to eq(%w{cool beans})
|
|
26
|
+
|
|
27
|
+
expect {
|
|
28
|
+
module BankRole
|
|
29
|
+
include Reindeer::Role
|
|
30
|
+
requires :money
|
|
31
|
+
end
|
|
32
|
+
class SeventhFail < Reindeer
|
|
33
|
+
with BankRole
|
|
34
|
+
meta.compose!
|
|
35
|
+
end
|
|
36
|
+
}.to raise_error(Reindeer::Role::RoleError)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it 'should compose multiple roles' do
|
|
40
|
+
module SecondRole
|
|
41
|
+
include Reindeer::Role
|
|
42
|
+
has :foo
|
|
43
|
+
def bar; 'two'; end
|
|
44
|
+
end
|
|
45
|
+
module ThirdRole
|
|
46
|
+
include Reindeer::Role
|
|
47
|
+
has :baz
|
|
48
|
+
def quux; 'four'; end
|
|
49
|
+
end
|
|
50
|
+
class FourteenthOne < Reindeer
|
|
51
|
+
with SecondRole
|
|
52
|
+
with ThirdRole
|
|
53
|
+
meta.compose!
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
obj = FourteenthOne.new(foo: 'one', baz: 'three')
|
|
57
|
+
expect(obj.foo).to eq('one')
|
|
58
|
+
expect(obj.bar).to eq('two')
|
|
59
|
+
expect(obj.baz).to eq('three')
|
|
60
|
+
expect(obj.quux).to eq('four')
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it 'should know if an object does a role' do
|
|
64
|
+
module DoesRole
|
|
65
|
+
include Reindeer::Role
|
|
66
|
+
end
|
|
67
|
+
class ThatDoesARole < Reindeer
|
|
68
|
+
with DoesRole
|
|
69
|
+
meta.compose!
|
|
70
|
+
end
|
|
71
|
+
class ThatDoesNoRole < Reindeer; end
|
|
72
|
+
|
|
73
|
+
expect(ThatDoesARole.does? DoesRole).to be_true
|
|
74
|
+
expect(ThatDoesARole.new.does? DoesRole).to be_true
|
|
75
|
+
|
|
76
|
+
expect(ThatDoesNoRole.does? DoesRole).to be_false
|
|
77
|
+
expect(ThatDoesNoRole.new.does? DoesRole).to be_false
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
require 'reindeer'
|
|
2
|
+
|
|
3
|
+
describe 'Reindeer types' do
|
|
4
|
+
it 'should constrain attributes' do
|
|
5
|
+
class QuietWord < Reindeer
|
|
6
|
+
with Reindeer::Role::TypeConstraint
|
|
7
|
+
|
|
8
|
+
def verify(val)
|
|
9
|
+
val.downcase == val
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
meta.compose!
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class SoftlySpoken < Reindeer
|
|
16
|
+
has :start, is_a: String, type_of: QuietWord
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
obj = SoftlySpoken.new(start: 'foo')
|
|
20
|
+
expect(obj.start).to eq('foo')
|
|
21
|
+
|
|
22
|
+
expect {
|
|
23
|
+
obj = SoftlySpoken.new(start: 'LOUD NOISES')
|
|
24
|
+
}.to raise_error(Reindeer::TypeConstraint::Invalid)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'should have useful a error message' do
|
|
28
|
+
class LoudWord < Reindeer
|
|
29
|
+
with Reindeer::Role::TypeConstraint
|
|
30
|
+
|
|
31
|
+
def verify(val)
|
|
32
|
+
val.upcase == val
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def error_message_for(val)
|
|
36
|
+
"THE VALUE '#{val}' WASN'T LOUD ENOUGH"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
meta.compose!
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
class SergeantMajor < Reindeer
|
|
43
|
+
has :order, is_a: String, type_of: LoudWord
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
obj = SergeantMajor.new(order: 'TEN HUT')
|
|
47
|
+
expect(obj.order).to eq('TEN HUT')
|
|
48
|
+
|
|
49
|
+
expect {
|
|
50
|
+
SergeantMajor.new(order: 'quiet please')
|
|
51
|
+
}.to raise_error(Reindeer::TypeConstraint::Invalid, /WASN'T LOUD ENOUGH/)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
require 'reindeer'
|
|
2
|
+
|
|
3
|
+
describe 'Reindeer' do
|
|
4
|
+
it 'should have a meta per subclass' do
|
|
5
|
+
class FourthOne < Reindeer; end
|
|
6
|
+
class FifthOne < Reindeer; end
|
|
7
|
+
expect(FourthOne.new.meta == FifthOne.new.meta).to be_false
|
|
8
|
+
expect(FourthOne.new.meta).to eql(FourthOne.meta)
|
|
9
|
+
end
|
|
10
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: reindeer
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
prerelease:
|
|
6
|
+
platform: ruby
|
|
7
|
+
authors:
|
|
8
|
+
- Dan Brook
|
|
9
|
+
autorequire:
|
|
10
|
+
bindir: bin
|
|
11
|
+
cert_chain: []
|
|
12
|
+
date: 2013-03-31 00:00:00.000000000 Z
|
|
13
|
+
dependencies:
|
|
14
|
+
- !ruby/object:Gem::Dependency
|
|
15
|
+
name: rake
|
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
|
17
|
+
none: false
|
|
18
|
+
requirements:
|
|
19
|
+
- - ~>
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: 0.9.2
|
|
22
|
+
type: :development
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
none: false
|
|
26
|
+
requirements:
|
|
27
|
+
- - ~>
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
version: 0.9.2
|
|
30
|
+
- !ruby/object:Gem::Dependency
|
|
31
|
+
name: rspec
|
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
|
33
|
+
none: false
|
|
34
|
+
requirements:
|
|
35
|
+
- - ~>
|
|
36
|
+
- !ruby/object:Gem::Version
|
|
37
|
+
version: '2'
|
|
38
|
+
type: :development
|
|
39
|
+
prerelease: false
|
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
41
|
+
none: false
|
|
42
|
+
requirements:
|
|
43
|
+
- - ~>
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: '2'
|
|
46
|
+
description: Adds featureful attributes, roles and type constraints
|
|
47
|
+
email: dan@broquaint.com
|
|
48
|
+
executables: []
|
|
49
|
+
extensions: []
|
|
50
|
+
extra_rdoc_files: []
|
|
51
|
+
files:
|
|
52
|
+
- .rspec
|
|
53
|
+
- CHANGELOG.md
|
|
54
|
+
- Gemfile
|
|
55
|
+
- Gemfile.lock
|
|
56
|
+
- LICENSE
|
|
57
|
+
- README.md
|
|
58
|
+
- Rakefile
|
|
59
|
+
- lib/reindeer.rb
|
|
60
|
+
- lib/reindeer/meta.rb
|
|
61
|
+
- lib/reindeer/meta/attribute.rb
|
|
62
|
+
- lib/reindeer/role.rb
|
|
63
|
+
- lib/reindeer/role/typeconstraint.rb
|
|
64
|
+
- lib/reindeer/version.rb
|
|
65
|
+
- reindeer.gemspec
|
|
66
|
+
- spec/reindeer/attributes_spec.rb
|
|
67
|
+
- spec/reindeer/construction_spec.rb
|
|
68
|
+
- spec/reindeer/roles_spec.rb
|
|
69
|
+
- spec/reindeer/types_spec.rb
|
|
70
|
+
- spec/reindeer_spec.rb
|
|
71
|
+
homepage: http://github.com/broquaint/reindeer
|
|
72
|
+
licenses: []
|
|
73
|
+
post_install_message:
|
|
74
|
+
rdoc_options: []
|
|
75
|
+
require_paths:
|
|
76
|
+
- lib
|
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
78
|
+
none: false
|
|
79
|
+
requirements:
|
|
80
|
+
- - ! '>='
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '1.9'
|
|
83
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
84
|
+
none: false
|
|
85
|
+
requirements:
|
|
86
|
+
- - ! '>='
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '0'
|
|
89
|
+
requirements: []
|
|
90
|
+
rubyforge_project:
|
|
91
|
+
rubygems_version: 1.8.24
|
|
92
|
+
signing_key:
|
|
93
|
+
specification_version: 3
|
|
94
|
+
summary: Moose sugar in Ruby
|
|
95
|
+
test_files:
|
|
96
|
+
- spec/reindeer/attributes_spec.rb
|
|
97
|
+
- spec/reindeer/construction_spec.rb
|
|
98
|
+
- spec/reindeer/roles_spec.rb
|
|
99
|
+
- spec/reindeer/types_spec.rb
|
|
100
|
+
- spec/reindeer_spec.rb
|