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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9de74e43eb48f696e8284882f70046f2e824f1bf
4
- data.tar.gz: 4de24fa14eb525838acb447c0d4bb79d3f7eff04
3
+ metadata.gz: a06a22bea52408ffd64b85a1adaca09f92d9c01b
4
+ data.tar.gz: ef14964e9c023946f7fefb3c6b68eabe2b074007
5
5
  SHA512:
6
- metadata.gz: f6ebc7cf450ae0324f32f96bf8e6fe4349e10c8d65a32bc37f341cf872d38b508475a41bf198a8af93cdec6bf5628489206c6dc7ed44ac5dac7e01b06be4ef5e
7
- data.tar.gz: f7b3b93aa9b066835095184cb2265f5faddd4c857bb60494e6dd0775bb4822868039bfcec6673498bd5c9a064f0dcb3270f86083a340378e98eb61546f5c4117
6
+ metadata.gz: ce1b018403485200bf1348d45eb77c4d4e3c49158233c351b573eb1d4fead11843c036a7c8c3061b2f87700d79a46818273281bf635956030f978a879cb03645
7
+ data.tar.gz: c43d375dfa1f161163da622010932aab022736e155f850669fc37258e513db5bcfc251f3dea6160c277e290e7bb0b5373bb1721a302542796838fc47200b1e02
data/.travis.yml CHANGED
@@ -1,8 +1,9 @@
1
1
  sudo: false
2
2
  language: ruby
3
3
  rvm:
4
- - 2.3.0
5
- - 2.4.0
4
+ - 2.3
5
+ - 2.4
6
+ - 2.5
6
7
 
7
8
  before_install: gem install bundler
8
9
  notifications:
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 => Fixnum,
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`/`Fixnum` - either one will cause a `Fixnum` conversion
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
- # coding: utf-8
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/core_ext/string"
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
- return unless attributes.is_a?(Hash)
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 :eql? :==
130
+ alias eql? ==
131
131
 
132
132
  def to_h
133
133
  hash = {}
134
- (#{simple.map { |n| n.keys.first } + nested.map { |n| n.keys.first }}).each do |name|
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
- private :__nested_attributes
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.is_a?(Hash)
243
- assign_attributes(attributes)
244
-
245
- accepted = to_h.keys
251
+ return unless __initialize(attributes)
246
252
  attributes.each do |name, _|
247
- next if accepted.include?(name.respond_to?(:to_sym) ? name.to_sym : name)
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Class2
2
- VERSION = "0.3.0"
4
+ VERSION = "0.4.0"
3
5
  end
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.3.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: 2017-12-05 00:00:00.000000000 Z
11
+ date: 2018-05-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport