class2 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +3 -2
- data/Changes +7 -0
- data/README.md +28 -3
- data/lib/class2.rb +147 -17
- data/lib/class2/version.rb +3 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a06a22bea52408ffd64b85a1adaca09f92d9c01b
|
4
|
+
data.tar.gz: ef14964e9c023946f7fefb3c6b68eabe2b074007
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ce1b018403485200bf1348d45eb77c4d4e3c49158233c351b573eb1d4fead11843c036a7c8c3061b2f87700d79a46818273281bf635956030f978a879cb03645
|
7
|
+
data.tar.gz: c43d375dfa1f161163da622010932aab022736e155f850669fc37258e513db5bcfc251f3dea6160c277e290e7bb0b5373bb1721a302542796838fc47200b1e02
|
data/.travis.yml
CHANGED
data/Changes
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
2018-05-27 v0.4.0
|
2
|
+
--------------------
|
3
|
+
* Fix conversion bug when a module is used as a type specifier
|
4
|
+
* Fix Fixnum deprecation warnings
|
5
|
+
* Add support for JSON serialization and accessor formats that differ from definition
|
6
|
+
* Add method that modules can use for initialization
|
7
|
+
|
1
8
|
2017-12-04 v0.3.0
|
2
9
|
--------------------
|
3
10
|
* For attributes with a type of String don't convert nil to an empty string
|
data/README.md
CHANGED
@@ -30,7 +30,7 @@ Each of these classes are created with
|
|
30
30
|
```rb
|
31
31
|
class2 :user => {
|
32
32
|
:name => String,
|
33
|
-
:age =>
|
33
|
+
:age => Integer,
|
34
34
|
:addresses => [
|
35
35
|
:city, :state, :zip, # No explicit types for these
|
36
36
|
:country => {
|
@@ -95,13 +95,37 @@ response = [
|
|
95
95
|
}
|
96
96
|
]
|
97
97
|
|
98
|
-
class2 :commit => response.first
|
98
|
+
class2 :commit => response.first do
|
99
|
+
include Class2::SnakeCase::JSON
|
100
|
+
end
|
99
101
|
|
100
102
|
commit = Commit.new(response.first)
|
101
103
|
commit.author.name # "sshaw"
|
102
104
|
commit.comment_count # 0
|
105
|
+
JSON.dump(commit)
|
103
106
|
```
|
104
107
|
|
108
|
+
If the JSON uses `camelCase` but you want your class to use `snake_case` you can do the following:
|
109
|
+
|
110
|
+
```rb
|
111
|
+
class2 "commit" => { "camelCase" => { "someKey" => 123, "anotherKey" => 456 } } do
|
112
|
+
include Class2::SnakeCase::Attributes # snake_case accessors
|
113
|
+
include Class2::LowerCamelCase::JSON # but serialize using camelCase
|
114
|
+
end
|
115
|
+
|
116
|
+
commit = Commit.new(:camel_case => { :some_key => 55 })
|
117
|
+
commit.camel_case.some_key # 55
|
118
|
+
|
119
|
+
commit = Commit.new(:camelCase => { :someKey => 55 })
|
120
|
+
commit.camel_case.some_key # 55
|
121
|
+
```
|
122
|
+
|
123
|
+
For more info on accessor formats and JSON see:
|
124
|
+
|
125
|
+
* [`Class2::SnakeCase`](https://www.rubydoc.info/gems/class2/Class2/SnakeCase)
|
126
|
+
* [`Class2::UpperCamelCase`](https://www.rubydoc.info/gems/class2/Class2/UpperCamelCase)
|
127
|
+
* [`Class2::LowerCamelCase`](https://www.rubydoc.info/gems/class2/Class2/LowerCamelCase)
|
128
|
+
|
105
129
|
### class2 API
|
106
130
|
|
107
131
|
The are 3 ways to use class2. Pick the one that suites your style and/or requirements:
|
@@ -142,7 +166,7 @@ You can use any of these classes or their instances in your class definitions:
|
|
142
166
|
* `DateTime`
|
143
167
|
* `Float`
|
144
168
|
* `Hash`
|
145
|
-
* `Integer
|
169
|
+
* `Integer`
|
146
170
|
* `TrueClass`/`FalseClass` - either one will cause a boolean conversion
|
147
171
|
|
148
172
|
Custom conversions are possible, just add the conversion to
|
@@ -221,6 +245,7 @@ Now an `ArgumentError` will be raised if anything but `id`, `name`, or
|
|
221
245
|
|
222
246
|
Also see [Customizations](#customizations).
|
223
247
|
|
248
|
+
|
224
249
|
## See Also
|
225
250
|
|
226
251
|
The Perl modules that served as inspiration:
|
data/lib/class2.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "date"
|
4
|
+
require "json"
|
4
5
|
require "active_support/core_ext/module"
|
5
|
-
require "active_support/
|
6
|
+
require "active_support/inflector"
|
6
7
|
|
7
8
|
require "class2/version"
|
8
9
|
|
@@ -37,7 +38,7 @@ class Class2
|
|
37
38
|
}
|
38
39
|
|
39
40
|
CONVERSIONS[FalseClass] = CONVERSIONS[TrueClass]
|
40
|
-
CONVERSIONS[Fixnum] = CONVERSIONS[Integer]
|
41
|
+
CONVERSIONS[Fixnum] = CONVERSIONS[Integer] if defined?(Fixnum)
|
41
42
|
CONVERSIONS.default = lambda { |v| v }
|
42
43
|
|
43
44
|
class << self
|
@@ -91,7 +92,7 @@ class Class2
|
|
91
92
|
else
|
92
93
|
# Type can be a class name or an instance
|
93
94
|
# If it's an instance, use its type
|
94
|
-
v = v.class unless v.is_a?(Class)
|
95
|
+
v = v.class unless v.is_a?(Class) || v.is_a?(Module)
|
95
96
|
simple << { k => v }
|
96
97
|
end
|
97
98
|
end
|
@@ -113,8 +114,7 @@ class Class2
|
|
113
114
|
|
114
115
|
klass = Class.new do
|
115
116
|
def initialize(attributes = nil)
|
116
|
-
|
117
|
-
assign_attributes(attributes)
|
117
|
+
__initialize(attributes)
|
118
118
|
end
|
119
119
|
|
120
120
|
class_eval <<-CODE, __FILE__, __LINE__
|
@@ -127,11 +127,11 @@ class Class2
|
|
127
127
|
to_h == other.to_h
|
128
128
|
end
|
129
129
|
|
130
|
-
alias
|
130
|
+
alias eql? ==
|
131
131
|
|
132
132
|
def to_h
|
133
133
|
hash = {}
|
134
|
-
|
134
|
+
self.class.__attributes.each do |name|
|
135
135
|
hash[name] = v = public_send(name)
|
136
136
|
# Don't turn nil into a Hash
|
137
137
|
next if v.nil? || !v.respond_to?(:to_h)
|
@@ -158,11 +158,13 @@ class Class2
|
|
158
158
|
hash
|
159
159
|
end
|
160
160
|
|
161
|
-
def __nested_attributes
|
161
|
+
def self.__nested_attributes
|
162
162
|
#{nested.map { |n| n.keys.first.to_sym }}.freeze
|
163
163
|
end
|
164
164
|
|
165
|
-
|
165
|
+
def self.__attributes
|
166
|
+
(#{simple.map { |n| n.keys.first.to_sym }} + __nested_attributes).freeze
|
167
|
+
end
|
166
168
|
CODE
|
167
169
|
|
168
170
|
simple.each do |cfg|
|
@@ -205,11 +207,18 @@ class Class2
|
|
205
207
|
# Do this last to allow for overriding the methods we define
|
206
208
|
class_eval(&block) unless block.nil?
|
207
209
|
|
210
|
+
protected
|
211
|
+
|
212
|
+
def __initialize(attributes)
|
213
|
+
return unless attributes.is_a?(Hash)
|
214
|
+
assign_attributes(attributes)
|
215
|
+
end
|
216
|
+
|
208
217
|
private
|
209
218
|
|
210
219
|
def assign_attributes(attributes)
|
211
220
|
attributes.each do |key, value|
|
212
|
-
if __nested_attributes.include?(key.respond_to?(:to_sym) ? key.to_sym : key) &&
|
221
|
+
if self.class.__nested_attributes.include?(key.respond_to?(:to_sym) ? key.to_sym : key) &&
|
213
222
|
(value.is_a?(Hash) || value.is_a?(Array))
|
214
223
|
|
215
224
|
name = key.to_s.classify
|
@@ -233,22 +242,143 @@ class Class2
|
|
233
242
|
|
234
243
|
#
|
235
244
|
# By default unknown arguments are ignored. <code>include<code>ing this will
|
236
|
-
# cause an ArgumentError to be raised if an attribute is unknown
|
245
|
+
# cause an ArgumentError to be raised if an attribute is unknown.
|
237
246
|
#
|
238
247
|
module StrictConstructor
|
239
248
|
def self.included(klass)
|
240
249
|
klass.class_eval do
|
241
250
|
def initialize(attributes = nil)
|
242
|
-
return unless attributes
|
243
|
-
assign_attributes(attributes)
|
244
|
-
|
245
|
-
accepted = to_h.keys
|
251
|
+
return unless __initialize(attributes)
|
246
252
|
attributes.each do |name, _|
|
247
|
-
next if
|
253
|
+
next if self.class.__attributes.include?(name.respond_to?(:to_sym) ? name.to_sym : name)
|
248
254
|
raise ArgumentError, "unknown attribute: #{name}"
|
249
255
|
end
|
250
256
|
end
|
251
257
|
end
|
252
258
|
end
|
253
259
|
end
|
260
|
+
|
261
|
+
#
|
262
|
+
# Support +CamelCase+ attributes. See Class2::SnakeCase.
|
263
|
+
#
|
264
|
+
module UpperCamelCase
|
265
|
+
module Attributes
|
266
|
+
def self.included(klass)
|
267
|
+
Util.convert_attributes(klass) { |v| v.camelize }
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
module JSON
|
272
|
+
def as_json(*)
|
273
|
+
Util.as_json(self, :camelize)
|
274
|
+
end
|
275
|
+
|
276
|
+
def to_json(*argz)
|
277
|
+
as_json.to_json(*argz)
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
#
|
283
|
+
# Support +camelCase+ attributes. See Class2::SnakeCase .
|
284
|
+
#
|
285
|
+
module LowerCamelCase
|
286
|
+
module Attributes
|
287
|
+
def self.included(klass)
|
288
|
+
Util.convert_attributes(klass) { |v| v.camelize(:lower) }
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
module JSON
|
293
|
+
def as_json(*)
|
294
|
+
Util.as_json(self, :camelize, :lower)
|
295
|
+
end
|
296
|
+
|
297
|
+
def to_json(*argz)
|
298
|
+
as_json.to_json(*argz)
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
#
|
304
|
+
# Use this when the class was not defined using a Hash with +snake_case+ keys
|
305
|
+
# but +snake_case+ is a desired access or serialization mechanism.
|
306
|
+
#
|
307
|
+
module SnakeCase
|
308
|
+
#
|
309
|
+
# Support +snake_case+ attributes.
|
310
|
+
# This will accept them in the constructor and return them via #to_h.
|
311
|
+
#
|
312
|
+
# The key format used to define the class will still be accepted and its accessors will
|
313
|
+
# remain.
|
314
|
+
#
|
315
|
+
module Attributes
|
316
|
+
def self.included(klass)
|
317
|
+
Util.convert_attributes(klass) { |v| v.underscore }
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
#
|
322
|
+
# Create JSON documents that have +snake_case+ properties.
|
323
|
+
# This will add #as_json and #to_json methods.
|
324
|
+
#
|
325
|
+
module JSON
|
326
|
+
def as_json(*)
|
327
|
+
Util.as_json(self, :underscore)
|
328
|
+
end
|
329
|
+
|
330
|
+
def to_json(*argz)
|
331
|
+
as_json.to_json(*argz)
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
module Util
|
337
|
+
def self.as_json(klass, *argz)
|
338
|
+
hash = {}
|
339
|
+
klass.to_h.each do |k, v|
|
340
|
+
if v.is_a?(Hash)
|
341
|
+
v = as_json(v, *argz)
|
342
|
+
elsif v.is_a?(Array)
|
343
|
+
v = v.map { |e| as_json(e, *argz) }
|
344
|
+
elsif v.respond_to?(:as_json)
|
345
|
+
v = v.as_json
|
346
|
+
end
|
347
|
+
|
348
|
+
hash[k.to_s.public_send(*argz)] = v
|
349
|
+
end
|
350
|
+
|
351
|
+
hash
|
352
|
+
end
|
353
|
+
|
354
|
+
def self.convert_attributes(klass)
|
355
|
+
klass.class_eval do
|
356
|
+
new_nested = []
|
357
|
+
new_attributes = []
|
358
|
+
|
359
|
+
__attributes.map do |old_name|
|
360
|
+
new_name = yield(old_name.to_s)
|
361
|
+
alias_method new_name, old_name
|
362
|
+
alias_method "#{new_name}=", "#{old_name}="
|
363
|
+
|
364
|
+
new_attributes << new_name.to_sym
|
365
|
+
new_nested << new_attributes.last if __nested_attributes.include?(old_name)
|
366
|
+
end
|
367
|
+
|
368
|
+
class_eval <<-CODE
|
369
|
+
def self.__attributes
|
370
|
+
#{new_attributes}.freeze
|
371
|
+
end
|
372
|
+
|
373
|
+
# We need both styles nere to support proper assignment of nested attributes... :(
|
374
|
+
def self.__nested_attributes
|
375
|
+
#{new_nested + __nested_attributes}.freeze
|
376
|
+
end
|
377
|
+
CODE
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
private_constant :Util
|
383
|
+
|
254
384
|
end
|
data/lib/class2/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: class2
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Skye Shaw
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-05-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|