thin_models 0.1.4
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/README.txt +4 -0
- data/lib/thin_models.rb +0 -0
- data/lib/thin_models/errors.rb +3 -0
- data/lib/thin_models/lazy_array.rb +226 -0
- data/lib/thin_models/struct.rb +186 -0
- data/lib/thin_models/struct/identity.rb +42 -0
- data/lib/thin_models/struct/typed.rb +34 -0
- data/lib/thin_models/version.rb +3 -0
- metadata +144 -0
data/README.txt
ADDED
data/lib/thin_models.rb
ADDED
File without changes
|
@@ -0,0 +1,226 @@
|
|
1
|
+
module ThinModels
|
2
|
+
# Exposes Enumerable and a subset of the interface of Array, but is lazily evaluated.
|
3
|
+
#
|
4
|
+
# The default constructor allows you to pass in an underlying Enumerable whose .each method will be
|
5
|
+
# used; or you can ignore this and override #each and #initialize yourself.
|
6
|
+
#
|
7
|
+
# You should also consider overriding #length if you have
|
8
|
+
# an optimised mechanism for evaluating it without doing a full iteration via #each, and
|
9
|
+
# overriding #slice_from_start_and_length if you have an optimised mechanism for iterating over a slice/sub-range of
|
10
|
+
# the array. This will be used to supply optimised versions of #[] / #slice
|
11
|
+
#
|
12
|
+
# Deliberately doesn't expose any mutation methods - is not intended to be a mutable data structure.
|
13
|
+
class LazyArray
|
14
|
+
include Enumerable
|
15
|
+
|
16
|
+
def initialize(enumerable=nil)
|
17
|
+
@enumerable = enumerable
|
18
|
+
end
|
19
|
+
|
20
|
+
def each(&b)
|
21
|
+
@enumerable.each(&b)
|
22
|
+
end
|
23
|
+
|
24
|
+
def inspect
|
25
|
+
"[ThinModels::LazyArray:...]"
|
26
|
+
end
|
27
|
+
|
28
|
+
# We recommend overriding this #length implementation (which is based on #each) with an efficient
|
29
|
+
# implementation. #size will use your #length, and #count uses #size where available, hence will
|
30
|
+
# use it too.
|
31
|
+
def length
|
32
|
+
length = 0; each {length += 1}; length
|
33
|
+
end
|
34
|
+
|
35
|
+
def size; length; end
|
36
|
+
|
37
|
+
def empty?
|
38
|
+
each {return false}
|
39
|
+
return true
|
40
|
+
end
|
41
|
+
|
42
|
+
def join(separator=$,)
|
43
|
+
result = ''; first = true
|
44
|
+
each do |x|
|
45
|
+
if first
|
46
|
+
first = false
|
47
|
+
else
|
48
|
+
result << separator if separator
|
49
|
+
end
|
50
|
+
result << x.to_s
|
51
|
+
end
|
52
|
+
result
|
53
|
+
end
|
54
|
+
|
55
|
+
# enables splat syntax: a, *b = lazy_array; foo(*lazy_array) etc.
|
56
|
+
alias :to_ary :to_a
|
57
|
+
|
58
|
+
def to_json(*p)
|
59
|
+
to_a.to_json(*p)
|
60
|
+
end
|
61
|
+
|
62
|
+
# We recommend overriding this inefficient implementation (which uses #each to traverse from
|
63
|
+
# the start until it reaches the desired range) with an efficient implementation.
|
64
|
+
#
|
65
|
+
# Returns an array for the requested slice; may return a slice shorter than requested where the
|
66
|
+
# array doesn't extend that far, but if the start index is greater than the total length, must return
|
67
|
+
# nil. This is consistent with Array#slice/[]
|
68
|
+
# eg: [][1..10] == nil, but [][0..10] == []
|
69
|
+
#
|
70
|
+
# Does not need to handle the other argument types (Range, single index) which Array#slice/[] takes.
|
71
|
+
def slice_from_start_and_length(start, length)
|
72
|
+
result = []
|
73
|
+
stop = start + length
|
74
|
+
index = 0
|
75
|
+
each do |item|
|
76
|
+
break if index >= stop
|
77
|
+
result << item if index >= start
|
78
|
+
index += 1
|
79
|
+
end
|
80
|
+
result if index >= start
|
81
|
+
end
|
82
|
+
|
83
|
+
# behaviour is consistent with Array#[], except it doesn't take negative indexes.
|
84
|
+
# uses slice_from_start_and_length to do the work.
|
85
|
+
def [](index_or_range, length=nil)
|
86
|
+
case index_or_range
|
87
|
+
when Range
|
88
|
+
start = index_or_range.begin
|
89
|
+
length = index_or_range.end - start
|
90
|
+
length += 1 unless index_or_range.exclude_end?
|
91
|
+
slice_from_start_and_length(start, length)
|
92
|
+
when Integer
|
93
|
+
if length
|
94
|
+
slice_from_start_and_length(index_or_range, length)
|
95
|
+
else
|
96
|
+
slice = slice_from_start_and_length(index_or_range, 1) and slice.first
|
97
|
+
end
|
98
|
+
else
|
99
|
+
raise ArgumentError
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
alias :slice :[]
|
104
|
+
|
105
|
+
def first
|
106
|
+
self[0]
|
107
|
+
end
|
108
|
+
|
109
|
+
def last
|
110
|
+
l = length
|
111
|
+
self[l-1] if l > 0
|
112
|
+
end
|
113
|
+
|
114
|
+
# map works lazily, resulting in a ThinModels::LazyArray::Mapped or a ThinModels::LazyArray::Memoized::Mapped (which additionally
|
115
|
+
# memoizes the mapped values)
|
116
|
+
def map(memoize=false, &b)
|
117
|
+
(memoize ? Memoized::Mapped : Mapped).new(self, &b)
|
118
|
+
end
|
119
|
+
|
120
|
+
class Mapped < ThinModels::LazyArray
|
121
|
+
def initialize(underlying, &block)
|
122
|
+
@underlying = underlying; @block = block
|
123
|
+
end
|
124
|
+
|
125
|
+
def each
|
126
|
+
@underlying.each {|x| yield @block.call(x)}
|
127
|
+
end
|
128
|
+
|
129
|
+
def length
|
130
|
+
@underlying.length
|
131
|
+
end
|
132
|
+
|
133
|
+
def slice_from_start_and_length(start, length)
|
134
|
+
@underlying.slice_from_start_and_length(start, length).map(&@block)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# Memoizes the #length of the array, but does not at present memoize the results of each or slice_from_start_and_length.
|
139
|
+
#
|
140
|
+
# #length will be memoized as a result of a direct call to #length (which uses an underlying #_length), or
|
141
|
+
# as a result of a full iteration via #each (which uses an underlying #_each)
|
142
|
+
#
|
143
|
+
# Your extension points are now #_each, #_length and #slice_from_start_and_length
|
144
|
+
class MemoizedLength < ThinModels::LazyArray
|
145
|
+
def each
|
146
|
+
length = 0
|
147
|
+
_each {|item| yield item; length += 1}
|
148
|
+
@length = length
|
149
|
+
self
|
150
|
+
end
|
151
|
+
|
152
|
+
alias :_length :length
|
153
|
+
def length
|
154
|
+
@length ||= _length
|
155
|
+
end
|
156
|
+
|
157
|
+
def inspect
|
158
|
+
if @length
|
159
|
+
"[ThinModels::LazyArray(length=#{@length}):...]"
|
160
|
+
else
|
161
|
+
"[ThinModels::LazyArray:...]"
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
|
167
|
+
# This additionally memoizes the full contents of the array once it's been fully each'd one time.
|
168
|
+
# The memoized full contents will then be used for future calls to #each (and hence all the other enumerable
|
169
|
+
# methods) and #[]. #to_a directly returns the memoized array once available.
|
170
|
+
#
|
171
|
+
# As with MemoizedLength, your extension points are now #_each, #_length and #slice_from_start_and_length
|
172
|
+
class Memoized < MemoizedLength
|
173
|
+
def each(&b)
|
174
|
+
if @to_a
|
175
|
+
@to_a.each(&b)
|
176
|
+
else
|
177
|
+
result = []
|
178
|
+
_each {|item| yield item; result << item}
|
179
|
+
@length = result.length
|
180
|
+
@to_a = result
|
181
|
+
end
|
182
|
+
self
|
183
|
+
end
|
184
|
+
|
185
|
+
def to_a
|
186
|
+
@to_a || super
|
187
|
+
end
|
188
|
+
alias :entries :to_a
|
189
|
+
alias :to_ary :to_a
|
190
|
+
|
191
|
+
def [](*p)
|
192
|
+
@to_a ? @to_a[*p] : super
|
193
|
+
end
|
194
|
+
alias :slice :[]
|
195
|
+
|
196
|
+
def inspect
|
197
|
+
if @to_ary
|
198
|
+
"[ThinModels::LazyArray: #{@to_ary.inspect[1..-1]}]"
|
199
|
+
elsif @length
|
200
|
+
"[ThinModels::LazyArray(length=#{@length}):...]"
|
201
|
+
else
|
202
|
+
"[ThinModels::LazyArray:...]"
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
# For when you want to map to a ThinModels::LazyArray which memoizes the results of the map
|
207
|
+
class Mapped < Memoized
|
208
|
+
def initialize(underlying, &block)
|
209
|
+
@underlying = underlying; @block = block
|
210
|
+
end
|
211
|
+
|
212
|
+
def _each
|
213
|
+
@underlying.each {|x| yield @block.call(x)}
|
214
|
+
end
|
215
|
+
|
216
|
+
def _length
|
217
|
+
@underlying.length
|
218
|
+
end
|
219
|
+
|
220
|
+
def slice_from_start_and_length(start, length)
|
221
|
+
@underlying.slice_from_start_and_length(start, length).map(&@block)
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
@@ -0,0 +1,186 @@
|
|
1
|
+
require 'thin_models/errors'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
module ThinModels
|
5
|
+
|
6
|
+
class Struct
|
7
|
+
def self.new_skipping_checks(values, &lazy_values)
|
8
|
+
new(values, true, &lazy_values)
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(values=nil, skip_checks=false, &lazy_values)
|
12
|
+
@values = values || {}
|
13
|
+
@lazy_values = lazy_values if lazy_values
|
14
|
+
check_attributes if values && !skip_checks
|
15
|
+
end
|
16
|
+
|
17
|
+
def check_attributes
|
18
|
+
attributes = self.class.attributes
|
19
|
+
@values.each_key do |attribute|
|
20
|
+
raise NameError, "no attribute #{attribute} in #{self.class}" unless attributes.include?(attribute)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# this allows 'dup' to work in a desirable way for these instances,
|
25
|
+
# ie use a dup'd properties hash instance for the dup, meaning it can
|
26
|
+
# be updated without affecting the state of the original.
|
27
|
+
def initialize_copy(other)
|
28
|
+
super
|
29
|
+
@values = @values.dup
|
30
|
+
end
|
31
|
+
|
32
|
+
def freeze
|
33
|
+
super
|
34
|
+
@values.freeze
|
35
|
+
end
|
36
|
+
|
37
|
+
def loaded_values
|
38
|
+
@values.dup
|
39
|
+
end
|
40
|
+
|
41
|
+
# This helps these structs work with ruby methods, like merge, which expect a Hash.
|
42
|
+
alias :to_hash :loaded_values
|
43
|
+
|
44
|
+
def attribute_loaded?(attribute)
|
45
|
+
@values.has_key?(attribute)
|
46
|
+
end
|
47
|
+
alias :has_key? :attribute_loaded?
|
48
|
+
|
49
|
+
attr_accessor :lazy_values
|
50
|
+
private :lazy_values=
|
51
|
+
|
52
|
+
def remove_lazy_values
|
53
|
+
remove_instance_variable(:@lazy_values) if instance_variable_defined?(:@lazy_values)
|
54
|
+
end
|
55
|
+
|
56
|
+
def has_lazy_values?
|
57
|
+
instance_variable_defined?(:@lazy_values)
|
58
|
+
end
|
59
|
+
|
60
|
+
def attributes
|
61
|
+
self.class.attributes
|
62
|
+
end
|
63
|
+
|
64
|
+
def loaded_attributes
|
65
|
+
@values.keys
|
66
|
+
end
|
67
|
+
alias :keys :loaded_attributes
|
68
|
+
|
69
|
+
def [](attribute)
|
70
|
+
if @values.has_key?(attribute)
|
71
|
+
@values[attribute]
|
72
|
+
else
|
73
|
+
raise NameError, "no attribute #{attribute} in #{self.class}" unless self.class.attributes.include?(attribute)
|
74
|
+
if @lazy_values
|
75
|
+
@values[attribute] = @lazy_values.call(self, attribute)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def fetch(attribute)
|
81
|
+
if @values.has_key?(attribute)
|
82
|
+
@values[attribute]
|
83
|
+
else
|
84
|
+
raise NameError, "no attribute #{attribute} in #{self.class}" unless self.class.attributes.include?(attribute)
|
85
|
+
if @lazy_values
|
86
|
+
@values[attribute] = @lazy_values.call(self, attribute)
|
87
|
+
else
|
88
|
+
raise PartialDataError, "attribute #{attribute} not loaded"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def []=(attribute, value)
|
94
|
+
raise NameError, "no attribute #{attribute.inspect} in #{self.class}" unless self.class.attributes.include?(attribute)
|
95
|
+
@values[attribute] = value
|
96
|
+
end
|
97
|
+
|
98
|
+
def merge(updated_values)
|
99
|
+
dup.merge!(updated_values)
|
100
|
+
end
|
101
|
+
|
102
|
+
def merge!(updated_values)
|
103
|
+
updated_values.to_hash.each_key do |attribute|
|
104
|
+
raise NameError, "no attribute #{attribute.inspect} in #{self.class}" unless attributes.include?(attribute)
|
105
|
+
end
|
106
|
+
@values.merge!(updated_values)
|
107
|
+
self
|
108
|
+
end
|
109
|
+
|
110
|
+
# Based on Matz's code for OpenStruct#inspect in the stdlib.
|
111
|
+
#
|
112
|
+
# Note the trick with the Thread-local :__inspect_key__, which ruby internals appear to
|
113
|
+
# use but isn't documented anywhere. If you use it in the same way the stdlib uses it,
|
114
|
+
# you can override inspect without breaking its cycle avoidant behaviour
|
115
|
+
def inspect
|
116
|
+
str = "#<#{self.class}"
|
117
|
+
|
118
|
+
ids = (Thread.current[:__inspect_key__] ||= [])
|
119
|
+
if ids.include?(object_id)
|
120
|
+
return str << ' ...>'
|
121
|
+
end
|
122
|
+
|
123
|
+
ids << object_id
|
124
|
+
begin
|
125
|
+
first = true
|
126
|
+
for k,v in @values
|
127
|
+
str << "," unless first
|
128
|
+
first = false
|
129
|
+
str << " #{k}=#{v.inspect}"
|
130
|
+
end
|
131
|
+
if @lazy_values
|
132
|
+
str << "," unless first
|
133
|
+
str << " ..."
|
134
|
+
end
|
135
|
+
return str << '>'
|
136
|
+
ensure
|
137
|
+
ids.pop
|
138
|
+
end
|
139
|
+
end
|
140
|
+
alias :to_s :inspect
|
141
|
+
|
142
|
+
def to_json(*p)
|
143
|
+
@values.merge(:json_class => self.class).to_json(*p)
|
144
|
+
end
|
145
|
+
|
146
|
+
def self.json_create(json_values)
|
147
|
+
values = {}
|
148
|
+
attributes.each {|a| values[a] = json_values[a.to_s] if json_values.has_key?(a.to_s)}
|
149
|
+
new(values)
|
150
|
+
end
|
151
|
+
|
152
|
+
|
153
|
+
class << self
|
154
|
+
def attributes
|
155
|
+
@attributes ||= (superclass < Struct ? superclass.attributes.dup : Set.new)
|
156
|
+
end
|
157
|
+
|
158
|
+
private
|
159
|
+
|
160
|
+
def attribute(attribute)
|
161
|
+
raise "Attribute #{attribute} already defined on #{self}" if self.attributes.include?(attribute)
|
162
|
+
self.attributes << attribute
|
163
|
+
class_eval <<-EOS, __FILE__, __LINE__+1
|
164
|
+
def #{attribute}
|
165
|
+
fetch(#{attribute.inspect})
|
166
|
+
end
|
167
|
+
EOS
|
168
|
+
class_eval <<-EOS, __FILE__, __LINE__+1
|
169
|
+
def #{attribute}=(value)
|
170
|
+
self[#{attribute.inspect}] = value
|
171
|
+
end
|
172
|
+
EOS
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# todo: add a ? to boolean getters
|
178
|
+
|
179
|
+
def self.Struct(*attributes)
|
180
|
+
Class.new(Struct) do
|
181
|
+
attributes.each {|a| attribute(a)}
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
require 'thin_models/struct/identity'
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'thin_models/struct'
|
2
|
+
|
3
|
+
module ThinModels
|
4
|
+
|
5
|
+
module Struct::IdentityMethods
|
6
|
+
def ==(other)
|
7
|
+
super || (other.is_a?(self.class) && (id = self.id) && other.id == id) || false
|
8
|
+
end
|
9
|
+
|
10
|
+
def hash
|
11
|
+
id ? id.hash : super
|
12
|
+
end
|
13
|
+
|
14
|
+
# this was: alias :eql? :==, but that ran into http://redmine.ruby-lang.org/issues/show/734
|
15
|
+
def eql?(other)
|
16
|
+
super || (other.is_a?(self.class) && (id = self.id) && other.id == id) || false
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Struct
|
21
|
+
class << self
|
22
|
+
private
|
23
|
+
def identity_attribute(name=:id)
|
24
|
+
attribute(name) unless attributes.include?(name)
|
25
|
+
alias_method(:id=, "#{name}=") unless name == :id
|
26
|
+
class_eval <<-EOS, __FILE__, __LINE__+1
|
27
|
+
def id
|
28
|
+
@values[#{name.inspect}]
|
29
|
+
end
|
30
|
+
EOS
|
31
|
+
include IdentityMethods
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.StructWithIdentity(*attributes)
|
37
|
+
Class.new(Struct) do
|
38
|
+
identity_attribute
|
39
|
+
attributes.each {|a| attribute(a)}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'thin_models/struct'
|
2
|
+
begin
|
3
|
+
require 'typisch'
|
4
|
+
rescue LoadError
|
5
|
+
raise LoadError, "The typisch gem is required if you want to use thin_models/struct/typed"
|
6
|
+
end
|
7
|
+
|
8
|
+
class ThinModels::Struct::Typed < ThinModels::Struct
|
9
|
+
include Typisch::Typed
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def type_available
|
13
|
+
type.property_names_to_types.map do |name, type|
|
14
|
+
attribute(name) unless attributes.include?(name) || method_defined?(name)
|
15
|
+
alias_method(:"#{name}?", name) if type.excluding_null.is_a?(Typisch::Type::Boolean)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# this will only type-check non-lazy properties -- not much point
|
21
|
+
# passing lazy properties if it's going to eagerly fetch them right
|
22
|
+
# away just to type-check them.
|
23
|
+
def check_attributes
|
24
|
+
self.class.type.property_names.each do |property|
|
25
|
+
type_check_property(property) if @values.has_key?(property)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def []=(attribute, value)
|
30
|
+
raise NameError, "no attribute #{attribute.inspect} in #{self.class}" unless self.class.attributes.include?(attribute)
|
31
|
+
@values[attribute] = value
|
32
|
+
type_check_property(attribute)
|
33
|
+
end
|
34
|
+
end
|
metadata
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: thin_models
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 19
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 4
|
10
|
+
version: 0.1.4
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Matthew Willson
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-11-12 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rake
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 3
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
version: "0"
|
32
|
+
type: :development
|
33
|
+
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: test-spec
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
hash: 3
|
43
|
+
segments:
|
44
|
+
- 0
|
45
|
+
version: "0"
|
46
|
+
type: :development
|
47
|
+
version_requirements: *id002
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: mocha
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
hash: 3
|
57
|
+
segments:
|
58
|
+
- 0
|
59
|
+
version: "0"
|
60
|
+
type: :development
|
61
|
+
version_requirements: *id003
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: autotest
|
64
|
+
prerelease: false
|
65
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
hash: 3
|
71
|
+
segments:
|
72
|
+
- 0
|
73
|
+
version: "0"
|
74
|
+
type: :development
|
75
|
+
version_requirements: *id004
|
76
|
+
- !ruby/object:Gem::Dependency
|
77
|
+
name: typisch
|
78
|
+
prerelease: false
|
79
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
80
|
+
none: false
|
81
|
+
requirements:
|
82
|
+
- - ~>
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
hash: 19
|
85
|
+
segments:
|
86
|
+
- 0
|
87
|
+
- 1
|
88
|
+
- 4
|
89
|
+
version: 0.1.4
|
90
|
+
type: :development
|
91
|
+
version_requirements: *id005
|
92
|
+
description:
|
93
|
+
email:
|
94
|
+
- matthew@playlouder.com
|
95
|
+
executables: []
|
96
|
+
|
97
|
+
extensions: []
|
98
|
+
|
99
|
+
extra_rdoc_files: []
|
100
|
+
|
101
|
+
files:
|
102
|
+
- lib/thin_models.rb
|
103
|
+
- lib/thin_models/version.rb
|
104
|
+
- lib/thin_models/struct.rb
|
105
|
+
- lib/thin_models/struct/typed.rb
|
106
|
+
- lib/thin_models/struct/identity.rb
|
107
|
+
- lib/thin_models/lazy_array.rb
|
108
|
+
- lib/thin_models/errors.rb
|
109
|
+
- README.txt
|
110
|
+
homepage:
|
111
|
+
licenses: []
|
112
|
+
|
113
|
+
post_install_message:
|
114
|
+
rdoc_options: []
|
115
|
+
|
116
|
+
require_paths:
|
117
|
+
- lib
|
118
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
119
|
+
none: false
|
120
|
+
requirements:
|
121
|
+
- - ">="
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
hash: 3
|
124
|
+
segments:
|
125
|
+
- 0
|
126
|
+
version: "0"
|
127
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
128
|
+
none: false
|
129
|
+
requirements:
|
130
|
+
- - ">="
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
hash: 3
|
133
|
+
segments:
|
134
|
+
- 0
|
135
|
+
version: "0"
|
136
|
+
requirements: []
|
137
|
+
|
138
|
+
rubyforge_project:
|
139
|
+
rubygems_version: 1.8.10
|
140
|
+
signing_key:
|
141
|
+
specification_version: 3
|
142
|
+
summary: Some convenience classes for 'thin models' -- pure domain model data objects which are devoid of persistence and other infrastructural concerns
|
143
|
+
test_files: []
|
144
|
+
|