class2 0.3.0 → 0.4.0
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 +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
|