json_resource 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +4 -0
- data/LICENSE +21 -0
- data/README.md +64 -0
- data/lib/json_resource/errors.rb +5 -0
- data/lib/json_resource/inflections.rb +78 -0
- data/lib/json_resource/model.rb +211 -0
- data/lib/json_resource/refinements.rb +209 -0
- data/lib/json_resource/version.rb +3 -0
- data/lib/json_resource.rb +9 -0
- metadata +66 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: dbada0c6c6821280079957d75f002a39fa338c6566a4daf7cda3650b98fb29fd
|
4
|
+
data.tar.gz: a232cf53b5e86dd6e9df328786c5db0ab150747284e11682d5b51bf426fef46c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1a8a3aae1d4e7b21df4e09cc5d8da6f807219e6fc3841c02ca200d9bce621aa222f5e4caa1ee61b190c6041d2b1269674dcaf9b3453e4d8ccdebf3bc41e3e518
|
7
|
+
data.tar.gz: ab5f72f1a266e1ba3e8ae993d951339c9f63c66a5960e241968931343233ce2bafdcf8667eb58378589e698b628573d0f4b2f9b54d427693667095c574348d5b
|
data/CHANGELOG.md
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2021 Matthias Grosser
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
[![Gem Version](https://badge.fury.io/rb/json_resource.svg)](http://badge.fury.io/rb/json_resource)
|
2
|
+
[![build](https://github.com/mtgrosser/json_resource/actions/workflows/build.yml/badge.svg)](https://github.com/mtgrosser/json_resource/actions/workflows/build.yml)
|
3
|
+
|
4
|
+
# json_resource – Create Ruby objects from JSON data
|
5
|
+
|
6
|
+
```ruby
|
7
|
+
class Post
|
8
|
+
include JsonResource::Model
|
9
|
+
|
10
|
+
attribute :id, type: :integer
|
11
|
+
attribute :body, type: :string
|
12
|
+
attribute :timestamp, type: :time
|
13
|
+
attribute :status_code, type: :integer, path: %w[status statusCode]
|
14
|
+
attribute :status_text, type: :string, path: %w[status statusText]
|
15
|
+
|
16
|
+
has_collection :comments
|
17
|
+
end
|
18
|
+
|
19
|
+
class Comment
|
20
|
+
include JsonResource::Model
|
21
|
+
|
22
|
+
attribute :author, type: :string
|
23
|
+
attribute :text, type: :string
|
24
|
+
attribute :timestamp, type: :time
|
25
|
+
end
|
26
|
+
```
|
27
|
+
|
28
|
+
```json
|
29
|
+
{
|
30
|
+
"posts": [
|
31
|
+
{
|
32
|
+
"id": 123,
|
33
|
+
"body": "Lorem ipsum",
|
34
|
+
"timestamp": "2021-12-08T18:43:00",
|
35
|
+
"status": {
|
36
|
+
"statusCode": 2,
|
37
|
+
"statusText": "published"
|
38
|
+
},
|
39
|
+
"comments": [
|
40
|
+
{
|
41
|
+
"author": "Mr. Spock",
|
42
|
+
"text": "Fascinating!",
|
43
|
+
"timestamp": "2350-05-01T19:00:00"
|
44
|
+
},
|
45
|
+
{
|
46
|
+
"author": "Chekov",
|
47
|
+
"text": "Warp 10",
|
48
|
+
"timestamp": "2350-05-01T20:00:00"
|
49
|
+
}
|
50
|
+
]
|
51
|
+
|
52
|
+
}
|
53
|
+
]
|
54
|
+
}
|
55
|
+
```
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
posts = Post.collection_from_json(json, root: 'posts')
|
59
|
+
|
60
|
+
posts.first.id => 123
|
61
|
+
posts.first.body => 'Lorem ipsum'
|
62
|
+
posts.first.status_text => 'published'
|
63
|
+
posts.first.comments.first.author => 'Mr. Spock'
|
64
|
+
```
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module JsonResource
|
2
|
+
module Inflections
|
3
|
+
|
4
|
+
class << self
|
5
|
+
attr_accessor :pluralizations, :singularizations, :irregulars, :uncountables, :acronyms
|
6
|
+
end
|
7
|
+
|
8
|
+
self.pluralizations = [
|
9
|
+
[/\z/, 's'],
|
10
|
+
[/s\z/i, 's'],
|
11
|
+
[/(ax|test)is\z/i, '\1es'],
|
12
|
+
[/(.*)us\z/i, '\1uses'],
|
13
|
+
[/(octop|vir|cact)us\z/i, '\1i'],
|
14
|
+
[/(octop|vir)i\z/i, '\1i'],
|
15
|
+
[/(alias|status)\z/i, '\1es'],
|
16
|
+
[/(buffal|domin|ech|embarg|her|mosquit|potat|tomat)o\z/i, '\1oes'],
|
17
|
+
[/(?<!b)um\z/i, '\1a'],
|
18
|
+
[/([ti])a\z/i, '\1a'],
|
19
|
+
[/sis\z/i, 'ses'],
|
20
|
+
[/(.*)(?:([^f]))fe*\z/i, '\1\2ves'],
|
21
|
+
[/(hive|proof)\z/i, '\1s'],
|
22
|
+
[/([^aeiouy]|qu)y\z/i, '\1ies'],
|
23
|
+
[/(x|ch|ss|sh)\z/i, '\1es'],
|
24
|
+
[/(stoma|epo)ch\z/i, '\1chs'],
|
25
|
+
[/(matr|vert|ind)(?:ix|ex)\z/i, '\1ices'],
|
26
|
+
[/([m|l])ouse\z/i, '\1ice'],
|
27
|
+
[/([m|l])ice\z/i, '\1ice'],
|
28
|
+
[/^(ox)\z/i, '\1en'],
|
29
|
+
[/^(oxen)\z/i, '\1'],
|
30
|
+
[/(quiz)\z/i, '\1zes'],
|
31
|
+
[/(.*)non\z/i, '\1na'],
|
32
|
+
[/(.*)ma\z/i, '\1mata'],
|
33
|
+
[/(.*)(eau|eaux)\z/, '\1eaux']]
|
34
|
+
|
35
|
+
self.singularizations = [
|
36
|
+
[/s\z/i, ''],
|
37
|
+
[/(n)ews\z/i, '\1ews'],
|
38
|
+
[/([ti])a\z/i, '\1um'],
|
39
|
+
[/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)\z/i, '\1\2sis'],
|
40
|
+
[/(^analy)(sis|ses)\z/i, '\1sis'],
|
41
|
+
[/([^f])ves\z/i, '\1fe'],
|
42
|
+
[/(hive)s\z/i, '\1'],
|
43
|
+
[/(tive)s\z/i, '\1'],
|
44
|
+
[/([lr])ves\z/i, '\1f'],
|
45
|
+
[/([^aeiouy]|qu)ies\z/i, '\1y'],
|
46
|
+
[/(s)eries\z/i, '\1eries'],
|
47
|
+
[/(m)ovies\z/i, '\1ovie'],
|
48
|
+
[/(ss)\z/i, '\1'],
|
49
|
+
[/(x|ch|ss|sh)es\z/i, '\1'],
|
50
|
+
[/([m|l])ice\z/i, '\1ouse'],
|
51
|
+
[/(us)(es)?\z/i, '\1'],
|
52
|
+
[/(o)es\z/i, '\1'],
|
53
|
+
[/(shoe)s\z/i, '\1'],
|
54
|
+
[/(cris|ax|test)(is|es)\z/i, '\1is'],
|
55
|
+
[/(octop|vir)(us|i)\z/i, '\1us'],
|
56
|
+
[/(alias|status)(es)?\z/i, '\1'],
|
57
|
+
[/^(ox)en/i, '\1'],
|
58
|
+
[/(vert|ind)ices\z/i, '\1ex'],
|
59
|
+
[/(matr)ices\z/i, '\1ix'],
|
60
|
+
[/(quiz)zes\z/i, '\1'],
|
61
|
+
[/(database)s\z/i, '\1']]
|
62
|
+
|
63
|
+
self.irregulars = [
|
64
|
+
['person', 'people'],
|
65
|
+
['man', 'men'],
|
66
|
+
['human', 'humans'],
|
67
|
+
['child', 'children'],
|
68
|
+
['sex', 'sexes'],
|
69
|
+
['foot', 'feet'],
|
70
|
+
['tooth', 'teeth'],
|
71
|
+
['goose', 'geese'],
|
72
|
+
['forum', 'forums']]
|
73
|
+
|
74
|
+
self.uncountables = %w[hovercraft moose deer milk rain Swiss grass equipment information rice money species series fish sheep jeans]
|
75
|
+
|
76
|
+
self.acronyms = {}
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,211 @@
|
|
1
|
+
module JsonResource
|
2
|
+
using JsonResource::Refinements
|
3
|
+
|
4
|
+
module Model
|
5
|
+
TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE', 'on', 'ON'].to_set
|
6
|
+
|
7
|
+
def self.included(base)
|
8
|
+
base.extend ClassMethods
|
9
|
+
base.class_attribute :attributes, :collections, :objects, :inflection
|
10
|
+
base.attributes = {}
|
11
|
+
base.collections = {}
|
12
|
+
base.objects = {}
|
13
|
+
base.inflection = :lower_camelcase
|
14
|
+
end
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
|
18
|
+
def from_json(obj, defaults: {}, root: nil)
|
19
|
+
return unless json = parse(obj)
|
20
|
+
json = json_dig(json, *root) if root
|
21
|
+
attrs = {}
|
22
|
+
self.attributes.each do |name, options|
|
23
|
+
if path = attribute_path(name)
|
24
|
+
value = json_dig(json, *path)
|
25
|
+
if !value.nil? && type = attribute_type(name)
|
26
|
+
value = cast_to(type, value)
|
27
|
+
end
|
28
|
+
attrs[name] = value
|
29
|
+
end
|
30
|
+
end
|
31
|
+
instance = new(defaults.merge(attrs.compact))
|
32
|
+
self.objects.each do |name, options|
|
33
|
+
if path = object_path(name) and obj = json_dig(json, *path)
|
34
|
+
instance.public_send("#{name}=", object_class(name).from_json(obj))
|
35
|
+
end
|
36
|
+
end
|
37
|
+
self.collections.each do |name, options|
|
38
|
+
collection = if path = collection_path(name) and obj = json_dig(json, *path)
|
39
|
+
instance.public_send("#{name}=", collection_class(name).collection_from_json(obj))
|
40
|
+
else
|
41
|
+
[]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
instance
|
45
|
+
end
|
46
|
+
|
47
|
+
def collection_from_json(obj, defaults: {}, root: nil)
|
48
|
+
json = parse(obj)
|
49
|
+
json = json_dig(json, root) if root
|
50
|
+
json.map { |hsh| from_json(hsh, defaults: defaults) }.compact
|
51
|
+
end
|
52
|
+
|
53
|
+
def basename
|
54
|
+
name.sub(/^.*::/, '')
|
55
|
+
end
|
56
|
+
|
57
|
+
[:attribute, :object, :collection].each do |method_name|
|
58
|
+
define_method "#{method_name}_names" do
|
59
|
+
send("#{method_name}s").keys
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
protected
|
64
|
+
|
65
|
+
def attribute(name, options = {})
|
66
|
+
self.attributes = attributes.merge(name.to_sym => options.symbolize_keys)
|
67
|
+
attribute_accessor_method name
|
68
|
+
end
|
69
|
+
|
70
|
+
def attributes(*args)
|
71
|
+
options = args.extract_options!
|
72
|
+
args.each { |arg| has_attribute arg, options }
|
73
|
+
end
|
74
|
+
|
75
|
+
def has_object(name, options = {})
|
76
|
+
self.objects = objects.merge(name.to_sym => options.symbolize_keys)
|
77
|
+
attr_accessor name
|
78
|
+
end
|
79
|
+
|
80
|
+
def has_collection(name, options = {})
|
81
|
+
self.collections = collections.merge(name.to_sym => options.symbolize_keys)
|
82
|
+
define_method "#{name}" do
|
83
|
+
instance_variable_get("@#{name}") or instance_variable_set("@#{name}", [])
|
84
|
+
end
|
85
|
+
define_method "#{name}=" do |assignment|
|
86
|
+
instance_variable_set("@#{name}", assignment) if assignment
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def attribute_accessor_method(name)
|
93
|
+
define_method("#{name}") { self[name] }
|
94
|
+
define_method("#{name}=") { |value| self[name] = value }
|
95
|
+
end
|
96
|
+
|
97
|
+
[:attribute, :object, :collection].each do |method_name|
|
98
|
+
define_method "#{method_name}_path" do |name|
|
99
|
+
options = send(method_name.to_s.pluralize)
|
100
|
+
Array(options[name].try(:[], :path)).presence || [inflect(name)]
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def attribute_type(name)
|
105
|
+
attributes[name] && attributes[name][:type]
|
106
|
+
end
|
107
|
+
|
108
|
+
def object_class(name)
|
109
|
+
if objects[name] && class_name = objects[name][:class_name]
|
110
|
+
class_name.constantize
|
111
|
+
else
|
112
|
+
name.to_s.classify.constantize
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def collection_class(name)
|
117
|
+
if collections[name] && class_name = collections[name][:class_name]
|
118
|
+
class_name.constantize
|
119
|
+
else
|
120
|
+
name.to_s.singularize.classify.constantize
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def cast_to(type, value) # only called for non-nil values
|
125
|
+
case type
|
126
|
+
when :string then value
|
127
|
+
when :integer then value.to_i
|
128
|
+
when :float then value.to_f
|
129
|
+
when :boolean then cast_to_boolean(value)
|
130
|
+
when :decimal then BigDecimal(value)
|
131
|
+
when :date then value.presence && Date.parse(value)
|
132
|
+
when :time then value.presence && Time.parse(value)
|
133
|
+
else
|
134
|
+
raise JsonResource::TypeCastError, "don't know how to cast #{value.inspect} to #{type}"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def cast_to_boolean(value)
|
139
|
+
if value.is_a?(String) && value.blank?
|
140
|
+
nil
|
141
|
+
else
|
142
|
+
TRUE_VALUES.include?(value)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def parse(obj)
|
147
|
+
case obj
|
148
|
+
when Hash, Array then obj
|
149
|
+
when String then JSON.parse(obj)
|
150
|
+
else
|
151
|
+
raise JsonResource::ParseError, "cannot parse #{obj.inspect}"
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def inflect(string)
|
156
|
+
string = string.to_s
|
157
|
+
case inflection
|
158
|
+
when :lower_camelcase
|
159
|
+
string.camelcase(:lower)
|
160
|
+
when :upper_camelcase
|
161
|
+
string.camelcase(:upper)
|
162
|
+
when :dasherize
|
163
|
+
string.underscore.dasherize
|
164
|
+
when nil
|
165
|
+
string.underscore
|
166
|
+
else
|
167
|
+
string.public_send(inflection)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def json_dig(obj, *path)
|
172
|
+
path.inject(obj) do |receiver, key|
|
173
|
+
next nil if receiver.nil?
|
174
|
+
if key.respond_to?(:match) and idx = key.match(/\A\[(?<idx>\d+)\]\z/).try(:[], :idx)
|
175
|
+
receiver[idx.to_i]
|
176
|
+
else
|
177
|
+
receiver[key]
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def initialize(attrs = {})
|
184
|
+
self.attributes = attrs if attrs
|
185
|
+
super()
|
186
|
+
end
|
187
|
+
|
188
|
+
def valid?
|
189
|
+
true
|
190
|
+
end
|
191
|
+
|
192
|
+
def attributes=(attrs)
|
193
|
+
attrs.each do |attr, value|
|
194
|
+
self.public_send("#{attr}=", value)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def attributes
|
199
|
+
@attributes ||= {}
|
200
|
+
end
|
201
|
+
|
202
|
+
def [](attr_name)
|
203
|
+
attributes[attr_name.to_sym]
|
204
|
+
end
|
205
|
+
|
206
|
+
def []=(attr_name, value)
|
207
|
+
attributes[attr_name.to_sym] = value
|
208
|
+
end
|
209
|
+
|
210
|
+
end
|
211
|
+
end
|
@@ -0,0 +1,209 @@
|
|
1
|
+
module JsonResource
|
2
|
+
module Refinements
|
3
|
+
|
4
|
+
refine Object do
|
5
|
+
|
6
|
+
def blank?
|
7
|
+
respond_to?(:empty?) ? !!empty? : !self
|
8
|
+
end
|
9
|
+
|
10
|
+
def present?
|
11
|
+
!blank?
|
12
|
+
end
|
13
|
+
|
14
|
+
def presence
|
15
|
+
self if present?
|
16
|
+
end
|
17
|
+
|
18
|
+
def try(*a, &b)
|
19
|
+
try!(*a, &b) if a.empty? || respond_to?(a.first)
|
20
|
+
end
|
21
|
+
|
22
|
+
def try!(*a, &b)
|
23
|
+
if a.empty? && block_given?
|
24
|
+
if b.arity == 0
|
25
|
+
instance_eval(&b)
|
26
|
+
else
|
27
|
+
yield self
|
28
|
+
end
|
29
|
+
else
|
30
|
+
public_send(*a, &b)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
refine NilClass do
|
37
|
+
|
38
|
+
def try(*args)
|
39
|
+
nil
|
40
|
+
end
|
41
|
+
|
42
|
+
def try!(*args)
|
43
|
+
nil
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
refine Class do
|
49
|
+
|
50
|
+
def class_attribute(*attrs)
|
51
|
+
attrs.each do |name|
|
52
|
+
define_singleton_method(name) { nil }
|
53
|
+
|
54
|
+
ivar = "@#{name}"
|
55
|
+
|
56
|
+
define_singleton_method("#{name}=") do |val|
|
57
|
+
singleton_class.class_eval do
|
58
|
+
undef_method(name) if method_defined?(name) || private_method_defined?(name)
|
59
|
+
define_method(name) { val }
|
60
|
+
end
|
61
|
+
|
62
|
+
if singleton_class?
|
63
|
+
class_eval do
|
64
|
+
undef_method(name) if method_defined?(name) || private_method_defined?(name)
|
65
|
+
define_method(name) do
|
66
|
+
if instance_variable_defined? ivar
|
67
|
+
instance_variable_get ivar
|
68
|
+
else
|
69
|
+
singleton_class.send name
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
val
|
75
|
+
end
|
76
|
+
|
77
|
+
undef_method(name) if method_defined?(name) || private_method_defined?(name)
|
78
|
+
define_method(name) do
|
79
|
+
if instance_variable_defined?(ivar)
|
80
|
+
instance_variable_get ivar
|
81
|
+
else
|
82
|
+
self.class.public_send name
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
attr_writer name
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
refine String do
|
93
|
+
|
94
|
+
def constantize
|
95
|
+
if blank? || !include?("::")
|
96
|
+
Object.const_get(self)
|
97
|
+
else
|
98
|
+
names = split("::")
|
99
|
+
|
100
|
+
# Trigger a built-in NameError exception including the ill-formed constant in the message.
|
101
|
+
Object.const_get(self) if names.empty?
|
102
|
+
|
103
|
+
# Remove the first blank element in case of '::ClassName' notation.
|
104
|
+
names.shift if names.size > 1 && names.first.empty?
|
105
|
+
|
106
|
+
names.inject(Object) do |constant, name|
|
107
|
+
if constant == Object
|
108
|
+
constant.const_get(name)
|
109
|
+
else
|
110
|
+
candidate = constant.const_get(name)
|
111
|
+
next candidate if constant.const_defined?(name, false)
|
112
|
+
next candidate unless Object.const_defined?(name)
|
113
|
+
|
114
|
+
# Go down the ancestors to check if it is owned directly. The check
|
115
|
+
# stops when we reach Object or the end of ancestors tree.
|
116
|
+
constant = constant.ancestors.inject(constant) do |const, ancestor|
|
117
|
+
break const if ancestor == Object
|
118
|
+
break ancestor if ancestor.const_defined?(name, false)
|
119
|
+
const
|
120
|
+
end
|
121
|
+
|
122
|
+
# owner is in Object, so raise
|
123
|
+
constant.const_get(name, false)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def camelcase(*separators)
|
130
|
+
case separators.first
|
131
|
+
when Symbol, TrueClass, FalseClass, NilClass
|
132
|
+
first_letter = separators.shift
|
133
|
+
end
|
134
|
+
|
135
|
+
separators = ['_', '\s'] if separators.empty?
|
136
|
+
|
137
|
+
str = self.dup
|
138
|
+
|
139
|
+
separators.each do |s|
|
140
|
+
str = str.gsub(/(?:#{s}+)([a-z])/){ $1.upcase }
|
141
|
+
end
|
142
|
+
|
143
|
+
case first_letter
|
144
|
+
when :upper, true
|
145
|
+
str = str.gsub(/(\A|\s)([a-z])/){ $1 + $2.upcase }
|
146
|
+
when :lower, false
|
147
|
+
str = str.gsub(/(\A|\s)([A-Z])/){ $1 + $2.downcase }
|
148
|
+
end
|
149
|
+
|
150
|
+
str
|
151
|
+
end
|
152
|
+
|
153
|
+
def underscore
|
154
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
155
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
156
|
+
tr('-', '_').
|
157
|
+
gsub(/\s/, '_').
|
158
|
+
gsub(/__+/, '_').
|
159
|
+
downcase
|
160
|
+
end
|
161
|
+
|
162
|
+
def dasherize
|
163
|
+
underscore.gsub('_', '-')
|
164
|
+
end
|
165
|
+
|
166
|
+
def pluralize
|
167
|
+
return dup if Inflections.uncountables.reverse.any? { |suffix| end_with?(suffix) }
|
168
|
+
Inflections.irregulars.reverse.each do |suffix, irregluar|
|
169
|
+
return sub(/#{Regexp.escape(suffix)}\z/, irregluar) if end_with?(suffix)
|
170
|
+
end
|
171
|
+
plural = dup
|
172
|
+
Inflections.pluralizations.reverse.each do |pattern, substitution|
|
173
|
+
return plural if plural.gsub!(pattern, substitution)
|
174
|
+
end
|
175
|
+
plural
|
176
|
+
end
|
177
|
+
|
178
|
+
def singularize
|
179
|
+
return dup if Inflections.uncountables.reverse.any? { |suffix| end_with?(suffix) }
|
180
|
+
Inflections.irregulars.reverse.each do |suffix, irregular|
|
181
|
+
return sub(/#{Regexp.escape(irregular)}\z/, suffix) if end_with?(irregular)
|
182
|
+
end
|
183
|
+
singular = dup
|
184
|
+
Inflections.singularizations.reverse.each do |pattern, substitution|
|
185
|
+
return singular if singular.gsub!(pattern, substitution)
|
186
|
+
end
|
187
|
+
singular
|
188
|
+
end
|
189
|
+
|
190
|
+
def camelize
|
191
|
+
camelcase(:upper)
|
192
|
+
end
|
193
|
+
|
194
|
+
def classify
|
195
|
+
sub(/.*\./, '').singularize.camelize
|
196
|
+
end
|
197
|
+
|
198
|
+
end
|
199
|
+
|
200
|
+
refine Hash do
|
201
|
+
|
202
|
+
def symbolize_keys
|
203
|
+
transform_keys(&:to_sym)
|
204
|
+
end
|
205
|
+
|
206
|
+
end
|
207
|
+
|
208
|
+
end
|
209
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'bigdecimal'
|
2
|
+
require 'json'
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
require_relative 'json_resource/version'
|
6
|
+
require_relative 'json_resource/errors'
|
7
|
+
require_relative 'json_resource/inflections'
|
8
|
+
require_relative 'json_resource/refinements'
|
9
|
+
require_relative 'json_resource/model'
|
metadata
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: json_resource
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Matthias Grosser
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-12-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: json
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
description:
|
28
|
+
email:
|
29
|
+
- mtgrosser@gmx.net
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- CHANGELOG.md
|
35
|
+
- LICENSE
|
36
|
+
- README.md
|
37
|
+
- lib/json_resource.rb
|
38
|
+
- lib/json_resource/errors.rb
|
39
|
+
- lib/json_resource/inflections.rb
|
40
|
+
- lib/json_resource/model.rb
|
41
|
+
- lib/json_resource/refinements.rb
|
42
|
+
- lib/json_resource/version.rb
|
43
|
+
homepage: https://github.com/mtgrosser/json_resource
|
44
|
+
licenses:
|
45
|
+
- MIT
|
46
|
+
metadata: {}
|
47
|
+
post_install_message:
|
48
|
+
rdoc_options: []
|
49
|
+
require_paths:
|
50
|
+
- lib
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: 2.6.0
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
requirements: []
|
62
|
+
rubygems_version: 3.1.4
|
63
|
+
signing_key:
|
64
|
+
specification_version: 4
|
65
|
+
summary: Create Ruby objects from JSON data
|
66
|
+
test_files: []
|