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.
Files changed (59) hide show
  1. checksums.yaml +15 -0
  2. data/README.md +445 -0
  3. data/lib/motion_virtus.rb +13 -0
  4. data/lib/project/attribute/accessor/builder.rb +69 -0
  5. data/lib/project/attribute/accessor/lazy_accessor.rb +39 -0
  6. data/lib/project/attribute/accessor.rb +100 -0
  7. data/lib/project/attribute/accessor_method.rb +73 -0
  8. data/lib/project/attribute/array.rb +24 -0
  9. data/lib/project/attribute/boolean.rb +52 -0
  10. data/lib/project/attribute/class.rb +23 -0
  11. data/lib/project/attribute/coercer.rb +43 -0
  12. data/lib/project/attribute/collection/coercible_writer.rb +83 -0
  13. data/lib/project/attribute/collection.rb +56 -0
  14. data/lib/project/attribute/date.rb +36 -0
  15. data/lib/project/attribute/date_time.rb +38 -0
  16. data/lib/project/attribute/decimal.rb +23 -0
  17. data/lib/project/attribute/default_value/from_callable.rb +37 -0
  18. data/lib/project/attribute/default_value/from_clonable.rb +37 -0
  19. data/lib/project/attribute/default_value/from_symbol.rb +37 -0
  20. data/lib/project/attribute/default_value.rb +49 -0
  21. data/lib/project/attribute/embedded_value/open_struct_coercer.rb +43 -0
  22. data/lib/project/attribute/embedded_value/struct_coercer.rb +42 -0
  23. data/lib/project/attribute/embedded_value.rb +69 -0
  24. data/lib/project/attribute/float.rb +30 -0
  25. data/lib/project/attribute/hash/coercible_writer.rb +78 -0
  26. data/lib/project/attribute/hash.rb +66 -0
  27. data/lib/project/attribute/integer.rb +27 -0
  28. data/lib/project/attribute/numeric.rb +25 -0
  29. data/lib/project/attribute/object.rb +13 -0
  30. data/lib/project/attribute/reader.rb +39 -0
  31. data/lib/project/attribute/set.rb +22 -0
  32. data/lib/project/attribute/string.rb +24 -0
  33. data/lib/project/attribute/symbol.rb +23 -0
  34. data/lib/project/attribute/time.rb +36 -0
  35. data/lib/project/attribute/writer/coercible.rb +45 -0
  36. data/lib/project/attribute/writer.rb +73 -0
  37. data/lib/project/attribute.rb +292 -0
  38. data/lib/project/attribute_set.rb +260 -0
  39. data/lib/project/class_inclusions.rb +41 -0
  40. data/lib/project/class_methods.rb +102 -0
  41. data/lib/project/configuration.rb +65 -0
  42. data/lib/project/const_missing_extensions.rb +16 -0
  43. data/lib/project/extensions.rb +101 -0
  44. data/lib/project/instance_methods.rb +165 -0
  45. data/lib/project/module_builder.rb +92 -0
  46. data/lib/project/module_extensions.rb +72 -0
  47. data/lib/project/stubs/date.rb +2 -0
  48. data/lib/project/stubs/date_time.rb +2 -0
  49. data/lib/project/stubs/decimal.rb +2 -0
  50. data/lib/project/stubs/ostruct.rb +149 -0
  51. data/lib/project/stubs/set.rb +767 -0
  52. data/lib/project/stubs.rb +5 -0
  53. data/lib/project/support/equalizer.rb +147 -0
  54. data/lib/project/support/options.rb +114 -0
  55. data/lib/project/support/type_lookup.rb +109 -0
  56. data/lib/project/value_object.rb +139 -0
  57. data/lib/project/version.rb +3 -0
  58. data/lib/project/virtus.rb +128 -0
  59. 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