motion_virtus 1.0.0.beta0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +15 -0
- data/README.md +445 -0
- data/lib/motion_virtus.rb +13 -0
- data/lib/project/attribute/accessor/builder.rb +69 -0
- data/lib/project/attribute/accessor/lazy_accessor.rb +39 -0
- data/lib/project/attribute/accessor.rb +100 -0
- data/lib/project/attribute/accessor_method.rb +73 -0
- data/lib/project/attribute/array.rb +24 -0
- data/lib/project/attribute/boolean.rb +52 -0
- data/lib/project/attribute/class.rb +23 -0
- data/lib/project/attribute/coercer.rb +43 -0
- data/lib/project/attribute/collection/coercible_writer.rb +83 -0
- data/lib/project/attribute/collection.rb +56 -0
- data/lib/project/attribute/date.rb +36 -0
- data/lib/project/attribute/date_time.rb +38 -0
- data/lib/project/attribute/decimal.rb +23 -0
- data/lib/project/attribute/default_value/from_callable.rb +37 -0
- data/lib/project/attribute/default_value/from_clonable.rb +37 -0
- data/lib/project/attribute/default_value/from_symbol.rb +37 -0
- data/lib/project/attribute/default_value.rb +49 -0
- data/lib/project/attribute/embedded_value/open_struct_coercer.rb +43 -0
- data/lib/project/attribute/embedded_value/struct_coercer.rb +42 -0
- data/lib/project/attribute/embedded_value.rb +69 -0
- data/lib/project/attribute/float.rb +30 -0
- data/lib/project/attribute/hash/coercible_writer.rb +78 -0
- data/lib/project/attribute/hash.rb +66 -0
- data/lib/project/attribute/integer.rb +27 -0
- data/lib/project/attribute/numeric.rb +25 -0
- data/lib/project/attribute/object.rb +13 -0
- data/lib/project/attribute/reader.rb +39 -0
- data/lib/project/attribute/set.rb +22 -0
- data/lib/project/attribute/string.rb +24 -0
- data/lib/project/attribute/symbol.rb +23 -0
- data/lib/project/attribute/time.rb +36 -0
- data/lib/project/attribute/writer/coercible.rb +45 -0
- data/lib/project/attribute/writer.rb +73 -0
- data/lib/project/attribute.rb +292 -0
- data/lib/project/attribute_set.rb +260 -0
- data/lib/project/class_inclusions.rb +41 -0
- data/lib/project/class_methods.rb +102 -0
- data/lib/project/configuration.rb +65 -0
- data/lib/project/const_missing_extensions.rb +16 -0
- data/lib/project/extensions.rb +101 -0
- data/lib/project/instance_methods.rb +165 -0
- data/lib/project/module_builder.rb +92 -0
- data/lib/project/module_extensions.rb +72 -0
- data/lib/project/stubs/date.rb +2 -0
- data/lib/project/stubs/date_time.rb +2 -0
- data/lib/project/stubs/decimal.rb +2 -0
- data/lib/project/stubs/ostruct.rb +149 -0
- data/lib/project/stubs/set.rb +767 -0
- data/lib/project/stubs.rb +5 -0
- data/lib/project/support/equalizer.rb +147 -0
- data/lib/project/support/options.rb +114 -0
- data/lib/project/support/type_lookup.rb +109 -0
- data/lib/project/value_object.rb +139 -0
- data/lib/project/version.rb +3 -0
- data/lib/project/virtus.rb +128 -0
- metadata +158 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
YjIyMGQ5NzI0ZDRiOTlhOGQwNzNmNTdlZTdhYTAxMTA1Zjg0OWUyZQ==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
ZTVhYWFmZTNkNjAxMTA1YjBjY2M0NDYzNTA1ZmFkOTE3ODczZDRmZA==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
NDM2NzFmNjMwOTRkY2RhODhiYjQzMWNjMzU3NGI5NzJkNDQ5NTBkMmU4YmNm
|
10
|
+
MzJkZjNmMmFhZjIxODUyOGRhZDgxNDczNmNjNTZiNjI2ZjQzYTY4ZTYxZTRl
|
11
|
+
OTg0OGViMjEyMDMwY2I4MWQxYTA2NzNkNTVhZTU1YTFhNzRkYjQ=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
MzlkYTYwMTgxMjVmM2Q1N2NhMWQxODYwODIwOTQ2MGU3ZWFmMjA4YmUwOTNi
|
14
|
+
YzQ2YTNiMDBlYThlMTczNjdlYzJjMmFjM2QyMDc3NTBhYzhhM2ZhMmI3MDVi
|
15
|
+
YTE0MGI5YmFjNTQ0OGYwODU2ZDFlOTEwMWMyYWM1NDI5YTAxYWI=
|
data/README.md
ADDED
@@ -0,0 +1,445 @@
|
|
1
|
+
# motion_virtus
|
2
|
+
|
3
|
+
A RubyMotion port of [solnic's](https://github.com/solnic) [virtus](https://github.com/solnic/virtus) library.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'motion_virtus'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install motion_virtus
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
### Using Virtus with Classes
|
22
|
+
|
23
|
+
You can create classes extended with virtus and define attributes:
|
24
|
+
|
25
|
+
``` ruby
|
26
|
+
class User
|
27
|
+
include Virtus
|
28
|
+
|
29
|
+
attribute :name, String
|
30
|
+
attribute :age, Integer
|
31
|
+
attribute :birthday, DateTime
|
32
|
+
end
|
33
|
+
|
34
|
+
user = User.new(:name => 'Piotr', :age => 29)
|
35
|
+
user.attributes # => { :name => "Piotr", :age => 29 }
|
36
|
+
|
37
|
+
user.name # => "Piotr"
|
38
|
+
|
39
|
+
user.age = '29' # => 29
|
40
|
+
user.age.class # => Fixnum
|
41
|
+
|
42
|
+
user.birthday = 'November 18th, 1983' # => #<DateTime: 1983-11-18T00:00:00+00:00 (4891313/2,0/1,2299161)>
|
43
|
+
|
44
|
+
# mass-assignment
|
45
|
+
user.attributes = { :name => 'Jane', :age => 21 }
|
46
|
+
user.name # => "Jane"
|
47
|
+
user.age # => 21
|
48
|
+
```
|
49
|
+
|
50
|
+
### Using Virtus with Modules
|
51
|
+
|
52
|
+
You can create modules extended with virtus and define attributes for later
|
53
|
+
inclusion in your classes:
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
module Name
|
57
|
+
include Virtus
|
58
|
+
|
59
|
+
attribute :name, String
|
60
|
+
end
|
61
|
+
|
62
|
+
module Age
|
63
|
+
include Virtus
|
64
|
+
|
65
|
+
attribute :age, Integer
|
66
|
+
end
|
67
|
+
|
68
|
+
class User
|
69
|
+
include Name, Age
|
70
|
+
end
|
71
|
+
|
72
|
+
user = User.new(:name => 'John', :age => '30')
|
73
|
+
```
|
74
|
+
|
75
|
+
### Dynamically Extending Instances
|
76
|
+
|
77
|
+
It's also possible to dynamically extend an object with Virtus:
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
class User
|
81
|
+
# nothing here
|
82
|
+
end
|
83
|
+
|
84
|
+
user = User.new
|
85
|
+
user.extend(Virtus)
|
86
|
+
user.attribute :name, String
|
87
|
+
user.name = 'John'
|
88
|
+
user.name # => 'John'
|
89
|
+
```
|
90
|
+
|
91
|
+
### Default Values
|
92
|
+
|
93
|
+
``` ruby
|
94
|
+
class Page
|
95
|
+
include Virtus
|
96
|
+
|
97
|
+
attribute :title, String
|
98
|
+
|
99
|
+
# default from a singleton value (integer in this case)
|
100
|
+
attribute :views, Integer, :default => 0
|
101
|
+
|
102
|
+
# default from a singleton value (boolean in this case)
|
103
|
+
attribute :published, Boolean, :default => false
|
104
|
+
|
105
|
+
# default from a callable object (proc in this case)
|
106
|
+
attribute :slug, String, :default => lambda { |page, attribute| page.title.downcase.gsub(' ', '-') }
|
107
|
+
|
108
|
+
# default from a method name as symbol
|
109
|
+
attribute :editor_title, String, :default => :default_editor_title
|
110
|
+
|
111
|
+
def default_editor_title
|
112
|
+
published? ? title : "UNPUBLISHED: #{title}"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
page = Page.new(:title => 'Virtus README')
|
117
|
+
page.slug # => 'virtus-readme'
|
118
|
+
page.views # => 0
|
119
|
+
page.published # => false
|
120
|
+
page.editor_title # => "UNPUBLISHED: Virtus README"
|
121
|
+
```
|
122
|
+
|
123
|
+
### Embedded Value
|
124
|
+
|
125
|
+
``` ruby
|
126
|
+
class City
|
127
|
+
include Virtus
|
128
|
+
|
129
|
+
attribute :name, String
|
130
|
+
end
|
131
|
+
|
132
|
+
class Address
|
133
|
+
include Virtus
|
134
|
+
|
135
|
+
attribute :street, String
|
136
|
+
attribute :zipcode, String
|
137
|
+
attribute :city, City
|
138
|
+
end
|
139
|
+
|
140
|
+
class User
|
141
|
+
include Virtus
|
142
|
+
|
143
|
+
attribute :name, String
|
144
|
+
attribute :address, Address
|
145
|
+
end
|
146
|
+
|
147
|
+
user = User.new(:address => {
|
148
|
+
:street => 'Street 1/2', :zipcode => '12345', :city => { :name => 'NYC' } })
|
149
|
+
|
150
|
+
user.address.street # => "Street 1/2"
|
151
|
+
user.address.city.name # => "NYC"
|
152
|
+
```
|
153
|
+
|
154
|
+
### Collection Member Coercions
|
155
|
+
|
156
|
+
``` ruby
|
157
|
+
# Support "primitive" classes
|
158
|
+
class Book
|
159
|
+
include Virtus
|
160
|
+
|
161
|
+
attribute :page_numbers, Array[Integer]
|
162
|
+
end
|
163
|
+
|
164
|
+
book = Book.new(:page_numbers => %w[1 2 3])
|
165
|
+
book.page_numbers # => [1, 2, 3]
|
166
|
+
|
167
|
+
# Support EmbeddedValues, too!
|
168
|
+
class Address
|
169
|
+
include Virtus
|
170
|
+
|
171
|
+
attribute :address, String
|
172
|
+
attribute :locality, String
|
173
|
+
attribute :region, String
|
174
|
+
attribute :postal_code, String
|
175
|
+
end
|
176
|
+
|
177
|
+
class PhoneNumber
|
178
|
+
include Virtus
|
179
|
+
|
180
|
+
attribute :number, String
|
181
|
+
end
|
182
|
+
|
183
|
+
class User
|
184
|
+
include Virtus
|
185
|
+
|
186
|
+
attribute :phone_numbers, Array[PhoneNumber]
|
187
|
+
attribute :addresses, Set[Address]
|
188
|
+
end
|
189
|
+
|
190
|
+
user = User.new(
|
191
|
+
:phone_numbers => [
|
192
|
+
{ :number => '212-555-1212' },
|
193
|
+
{ :number => '919-444-3265' } ],
|
194
|
+
:addresses => [
|
195
|
+
{ :address => '1234 Any St.', :locality => 'Anytown', :region => "DC", :postal_code => "21234" } ])
|
196
|
+
|
197
|
+
user.phone_numbers # => [#<PhoneNumber:0x007fdb2d3bef88 @number="212-555-1212">, #<PhoneNumber:0x007fdb2d3beb00 @number="919-444-3265">]
|
198
|
+
|
199
|
+
user.addresses # => #<Set: {#<Address:0x007fdb2d3be448 @address="1234 Any St.", @locality="Anytown", @region="DC", @postal_code="21234">}>
|
200
|
+
```
|
201
|
+
|
202
|
+
### Hash attributes coercion
|
203
|
+
|
204
|
+
``` ruby
|
205
|
+
class Package
|
206
|
+
include Virtus
|
207
|
+
|
208
|
+
attribute :dimensions, Hash[Symbol => Float]
|
209
|
+
end
|
210
|
+
|
211
|
+
package = Package.new(:dimensions => { 'width' => "2.2", :height => 2, "length" => 4.5 })
|
212
|
+
package.dimensions # => { :width => 2.2, :height => 2.0, :length => 4.5 }
|
213
|
+
```
|
214
|
+
|
215
|
+
### IMPORTANT note about member coercions
|
216
|
+
|
217
|
+
Virtus performs coercions only when a value is being assigned. If you mutate the value later on using its own
|
218
|
+
interfaces then coercion won't be triggered.
|
219
|
+
|
220
|
+
Here's an example:
|
221
|
+
|
222
|
+
``` ruby
|
223
|
+
class Book
|
224
|
+
include Virtus
|
225
|
+
|
226
|
+
attribute :title, String
|
227
|
+
end
|
228
|
+
|
229
|
+
class Library
|
230
|
+
include Virtus
|
231
|
+
|
232
|
+
attribute :books, Array[Book]
|
233
|
+
end
|
234
|
+
|
235
|
+
library = Library.new
|
236
|
+
|
237
|
+
# This will coerce Hash to a Book instance
|
238
|
+
library.books = [ { :title => 'Introduction to Virtus' } ]
|
239
|
+
|
240
|
+
# This WILL NOT COERCE the value because you mutate the books array with Array#<<
|
241
|
+
library.books << { :title => 'Another Introduction to Virtus' }
|
242
|
+
```
|
243
|
+
|
244
|
+
A suggested solution to this problem would be to introduce your own class instead of using Array and implement
|
245
|
+
mutation methods that perform coercions. For example:
|
246
|
+
|
247
|
+
``` ruby
|
248
|
+
class Book
|
249
|
+
include Virtus
|
250
|
+
|
251
|
+
attribute :title, String
|
252
|
+
end
|
253
|
+
|
254
|
+
class BookCollection < Array
|
255
|
+
def <<(book)
|
256
|
+
if book.kind_of?(Hash)
|
257
|
+
super(Book.new(book))
|
258
|
+
else
|
259
|
+
super
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
class Library
|
265
|
+
include Virtus
|
266
|
+
|
267
|
+
attribute :books, BookCollection[Book]
|
268
|
+
end
|
269
|
+
|
270
|
+
library = Library.new
|
271
|
+
library.books << { :title => 'Another Introduction to Virtus' }
|
272
|
+
```
|
273
|
+
|
274
|
+
### Value Objects
|
275
|
+
|
276
|
+
``` ruby
|
277
|
+
class GeoLocation
|
278
|
+
include Virtus::ValueObject
|
279
|
+
|
280
|
+
attribute :latitude, Float
|
281
|
+
attribute :longitude, Float
|
282
|
+
end
|
283
|
+
|
284
|
+
class Venue
|
285
|
+
include Virtus
|
286
|
+
|
287
|
+
attribute :name, String
|
288
|
+
attribute :location, GeoLocation
|
289
|
+
end
|
290
|
+
|
291
|
+
venue = Venue.new(
|
292
|
+
:name => 'Pub',
|
293
|
+
:location => { :latitude => 37.160317, :longitude => -98.437500 })
|
294
|
+
|
295
|
+
venue.location.latitude # => 37.160317
|
296
|
+
venue.location.longitude # => -98.4375
|
297
|
+
|
298
|
+
# Supports object's equality
|
299
|
+
|
300
|
+
venue_other = Venue.new(
|
301
|
+
:name => 'Other Pub',
|
302
|
+
:location => { :latitude => 37.160317, :longitude => -98.437500 })
|
303
|
+
|
304
|
+
venue.location === venue_other.location # => true
|
305
|
+
```
|
306
|
+
|
307
|
+
### Custom Coercions
|
308
|
+
|
309
|
+
``` ruby
|
310
|
+
require 'json'
|
311
|
+
|
312
|
+
# With a custom writer class
|
313
|
+
class JsonWriter < Virtus::Attribute::Writer::Coercible
|
314
|
+
def coerce(value)
|
315
|
+
value.is_a?(Hash) ? value : JSON.parse(value)
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
class User
|
320
|
+
include Virtus
|
321
|
+
|
322
|
+
attribute :info, Hash, :writer_class => JsonWriter
|
323
|
+
end
|
324
|
+
|
325
|
+
user = User.new
|
326
|
+
user.info = '{"email":"john@domain.com"}' # => {"email"=>"john@domain.com"}
|
327
|
+
user.info.class # => Hash
|
328
|
+
|
329
|
+
# With a custom attribute encapsulating coercion-specific configuration
|
330
|
+
class NoisyString < Virtus::Attribute::String
|
331
|
+
class UpperCase < Virtus::Attribute::Writer::Coercible
|
332
|
+
def coerce(value)
|
333
|
+
super.upcase
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
def self.writer_class(*)
|
338
|
+
UpperCase
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
class User
|
343
|
+
include Virtus
|
344
|
+
|
345
|
+
attribute :scream, NoisyString
|
346
|
+
end
|
347
|
+
|
348
|
+
user = User.new(:scream => 'hello world!')
|
349
|
+
user.scream # => "HELLO WORLD!"
|
350
|
+
```
|
351
|
+
|
352
|
+
### Private Attributes
|
353
|
+
|
354
|
+
``` ruby
|
355
|
+
class User
|
356
|
+
include Virtus
|
357
|
+
|
358
|
+
attribute :unique_id, String, :writer => :private
|
359
|
+
|
360
|
+
def set_unique_id(id)
|
361
|
+
self.unique_id = id
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
user = User.new(:unique_id => '1234-1234')
|
366
|
+
user.unique_id # => nil
|
367
|
+
|
368
|
+
user.unique_id = '1234-1234' # => NoMethodError: private method `unique_id='
|
369
|
+
|
370
|
+
user.set_unique_id('1234-1234')
|
371
|
+
user.unique_id # => '1234-1234'
|
372
|
+
```
|
373
|
+
|
374
|
+
Coercions
|
375
|
+
---------
|
376
|
+
|
377
|
+
Virtus uses [Coercible](https://github.com/solnic/coercible) for coercions. This
|
378
|
+
feature is turned on by default. You can turn it off for all attributes like that:
|
379
|
+
|
380
|
+
```ruby
|
381
|
+
# Turn coercions off globally
|
382
|
+
Virtus.coerce(false)
|
383
|
+
|
384
|
+
# ...or you can turn it off for a single attribute
|
385
|
+
class User
|
386
|
+
include Virtus
|
387
|
+
|
388
|
+
attribute :name, String, :coerce => false
|
389
|
+
end
|
390
|
+
```
|
391
|
+
|
392
|
+
You can configure coercers too:
|
393
|
+
|
394
|
+
```ruby
|
395
|
+
Virtus.coercer do |config|
|
396
|
+
config.string.boolean_map = { 'yup' => true, 'nope' => false }
|
397
|
+
end
|
398
|
+
|
399
|
+
# Virtus.coercer instance is used by default for all attributes.
|
400
|
+
# You *can* override it for a single attribute if you want:
|
401
|
+
|
402
|
+
my_cool_coercer = Coercible::Coercer.new do |config|
|
403
|
+
# some customization
|
404
|
+
end
|
405
|
+
|
406
|
+
class User
|
407
|
+
include Virtus
|
408
|
+
|
409
|
+
attribute :name, String, :coercer => my_cool_coercer
|
410
|
+
end
|
411
|
+
```
|
412
|
+
|
413
|
+
## Building modules with custom configuration
|
414
|
+
|
415
|
+
You can also build Virtus modules that contain their own configuration.
|
416
|
+
|
417
|
+
```ruby
|
418
|
+
YupNopeBooleans = Virtus.module { |mod|
|
419
|
+
mod.coerce = true
|
420
|
+
mod.string.boolean_map = { 'yup' => true, 'nope' => false }
|
421
|
+
}
|
422
|
+
|
423
|
+
class User
|
424
|
+
include YupNopeBooleans
|
425
|
+
|
426
|
+
attribute :name, String
|
427
|
+
attribute :admin, Boolean
|
428
|
+
end
|
429
|
+
|
430
|
+
# Or just include the module straight away ...
|
431
|
+
class User
|
432
|
+
include Virtus.module { |m| m.coerce = false }
|
433
|
+
|
434
|
+
attribute :name, String
|
435
|
+
attribute :admin, Boolean
|
436
|
+
end
|
437
|
+
```
|
438
|
+
|
439
|
+
## Contributing
|
440
|
+
|
441
|
+
1. Fork it
|
442
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
443
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
444
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
445
|
+
5. Create new Pull Request
|
@@ -0,0 +1,13 @@
|
|
1
|
+
unless defined?(Motion::Project::Config)
|
2
|
+
raise "This file must be required within a RubyMotion project Rakefile."
|
3
|
+
end
|
4
|
+
|
5
|
+
require 'motion_descendants_tracker'
|
6
|
+
require 'motion_coercible'
|
7
|
+
|
8
|
+
require 'motion-require'
|
9
|
+
Motion::Require.all(Dir.glob(File.expand_path('../project/**/*.rb', __FILE__)))
|
10
|
+
|
11
|
+
Motion::Project::App.setup do |app|
|
12
|
+
#configuration
|
13
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module Virtus
|
2
|
+
class Attribute
|
3
|
+
class Accessor
|
4
|
+
|
5
|
+
class Builder
|
6
|
+
|
7
|
+
def self.call(*args)
|
8
|
+
builder = new(*args)
|
9
|
+
builder.accessor
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(name, type, options = {})
|
13
|
+
@name = name
|
14
|
+
@type = type
|
15
|
+
@options = options
|
16
|
+
@primitive = options.fetch(:primitive, ::Object)
|
17
|
+
@visibility = determine_visibility
|
18
|
+
end
|
19
|
+
|
20
|
+
def accessor
|
21
|
+
accessor_class.new(reader, writer)
|
22
|
+
end
|
23
|
+
|
24
|
+
def accessor_class
|
25
|
+
@options[:lazy] ? Accessor::LazyAccessor : Accessor
|
26
|
+
end
|
27
|
+
|
28
|
+
def reader
|
29
|
+
reader_class.new(@name, reader_options)
|
30
|
+
end
|
31
|
+
|
32
|
+
def writer
|
33
|
+
writer_class.new(@name, writer_options)
|
34
|
+
end
|
35
|
+
|
36
|
+
def reader_class
|
37
|
+
@options.fetch(:reader_class) {
|
38
|
+
@type.reader_class(@primitive, @options)
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
def writer_class
|
43
|
+
@options.fetch(:writer_class) {
|
44
|
+
@type.writer_class(@primitive, @options)
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
def reader_options
|
49
|
+
@type.reader_options(@options).update(:visibility => @visibility[:reader])
|
50
|
+
end
|
51
|
+
|
52
|
+
def writer_options
|
53
|
+
@type.writer_options(@options).update(:visibility => @visibility[:writer])
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def determine_visibility
|
59
|
+
default_accessor = @options.fetch(:accessor, :public)
|
60
|
+
reader_visibility = @options.fetch(:reader, default_accessor)
|
61
|
+
writer_visibility = @options.fetch(:writer, default_accessor)
|
62
|
+
{ :reader => reader_visibility, :writer => writer_visibility }
|
63
|
+
end
|
64
|
+
|
65
|
+
end # class Builder
|
66
|
+
|
67
|
+
end # class Accessor
|
68
|
+
end # class Attribute
|
69
|
+
end # module Virtus
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Virtus
|
2
|
+
class Attribute
|
3
|
+
class Accessor
|
4
|
+
|
5
|
+
# Lazy accessor provides evaluating default values on first read
|
6
|
+
#
|
7
|
+
# @api private
|
8
|
+
class LazyAccessor < self
|
9
|
+
|
10
|
+
# Read attribute value and set default value if attribute is not set yet
|
11
|
+
#
|
12
|
+
# @see Accessor#get
|
13
|
+
#
|
14
|
+
# @return [Object]
|
15
|
+
#
|
16
|
+
# @api private
|
17
|
+
def get(instance)
|
18
|
+
if instance.instance_variable_defined?(reader.instance_variable_name)
|
19
|
+
super
|
20
|
+
else
|
21
|
+
value = writer.default_value.call(instance, self)
|
22
|
+
writer.call(instance, value)
|
23
|
+
value
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Return if the accessor is lazy
|
28
|
+
#
|
29
|
+
# @return [TrueClass]
|
30
|
+
#
|
31
|
+
# @api private
|
32
|
+
def lazy?
|
33
|
+
true
|
34
|
+
end
|
35
|
+
end # LazyAccessor
|
36
|
+
|
37
|
+
end # class Accessor
|
38
|
+
end # class Attribute
|
39
|
+
end # module Virtus
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module Virtus
|
2
|
+
class Attribute
|
3
|
+
|
4
|
+
# Accessor object providing reader and writer methods
|
5
|
+
#
|
6
|
+
# @api private
|
7
|
+
class Accessor
|
8
|
+
#include Adamantium::Flat
|
9
|
+
|
10
|
+
# Return reader
|
11
|
+
#
|
12
|
+
# @return [Reader]
|
13
|
+
#
|
14
|
+
# @api private
|
15
|
+
attr_reader :reader
|
16
|
+
|
17
|
+
# Return writer
|
18
|
+
#
|
19
|
+
# @return [Writer]
|
20
|
+
#
|
21
|
+
# @api private
|
22
|
+
attr_reader :writer
|
23
|
+
|
24
|
+
# Build an accessor instance
|
25
|
+
#
|
26
|
+
# @param [Symbol] name
|
27
|
+
#
|
28
|
+
# @param [Class] type
|
29
|
+
#
|
30
|
+
# @param [Hash] options
|
31
|
+
#
|
32
|
+
# @return [Accessor]
|
33
|
+
#
|
34
|
+
# @api private
|
35
|
+
def self.build(*args)
|
36
|
+
Builder.call(*args)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Initialize a new accessor instance
|
40
|
+
#
|
41
|
+
# @param [Reader]
|
42
|
+
#
|
43
|
+
# @param [Writer]
|
44
|
+
#
|
45
|
+
# @return [undefined]
|
46
|
+
#
|
47
|
+
# @api private
|
48
|
+
def initialize(reader, writer)
|
49
|
+
@reader, @writer = reader, writer
|
50
|
+
end
|
51
|
+
|
52
|
+
# Get a variable value from an object
|
53
|
+
#
|
54
|
+
# @return [Object]
|
55
|
+
#
|
56
|
+
# @api private
|
57
|
+
def get(instance)
|
58
|
+
reader.call(instance)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Set a variable on an object
|
62
|
+
#
|
63
|
+
# @return [Object]
|
64
|
+
#
|
65
|
+
# @api private
|
66
|
+
def set(*args)
|
67
|
+
writer.call(*args)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Return if reader method is public
|
71
|
+
#
|
72
|
+
# @return [Boolean]
|
73
|
+
#
|
74
|
+
# @api private
|
75
|
+
def public_reader?
|
76
|
+
reader.public?
|
77
|
+
end
|
78
|
+
|
79
|
+
# Return if writer method is public
|
80
|
+
#
|
81
|
+
# @return [Boolean]
|
82
|
+
#
|
83
|
+
# @api private
|
84
|
+
def public_writer?
|
85
|
+
writer.public?
|
86
|
+
end
|
87
|
+
|
88
|
+
# Return if this accessor is lazy
|
89
|
+
#
|
90
|
+
# @return [FalseClass]
|
91
|
+
#
|
92
|
+
# @api private
|
93
|
+
def lazy?
|
94
|
+
false
|
95
|
+
end
|
96
|
+
|
97
|
+
end # class Accessor
|
98
|
+
|
99
|
+
end # class Attribute
|
100
|
+
end # module Virtus
|