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