jsonity 1.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7837ec79aa7e71a84da9f8fda957c0217bd2691b
4
+ data.tar.gz: 26ad97f8e6d9b9dcd3dad8c0155eab31a80c8d6b
5
+ SHA512:
6
+ metadata.gz: e7535b1ce03cb5c6cb877aaf91fa0676f93eda13bc706db8ddeb5169bc443853f0112893dcd2cc20d5a60264d2226231a9b83dca599e86815cb75f37c5e64208
7
+ data.tar.gz: 7b7cff2ba3ece3d3a5be9a9eb002ce8ebdc9c534537120cbc74736be1079b999621a9b90893c5c231fe5b0c999c33eb435196fa2a849bf8b9f604651059ca268
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Yuki Iwanaga
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,354 @@
1
+ Jsonity
2
+ =======
3
+
4
+ **The most natural language for building JSON in Ruby**
5
+
6
+
7
+ Overview
8
+ --------
9
+
10
+ ```ruby
11
+ @meta_pagination = ->(t) {
12
+ t.meta!(inherit: true) { |meta|
13
+ meta.total_pages
14
+ meta.current_page
15
+ }
16
+ }
17
+
18
+ Jsonity.build { |t|
19
+ t <= @users
20
+
21
+ t[].users!(inherit: true) { |user|
22
+ user.id
23
+ user.age
24
+ user.full_name { |u| [u.first_name, u.last_name].join ' ' }
25
+
26
+ user.avatar? { |avatar|
27
+ avatar.image_url
28
+ }
29
+ }
30
+
31
+ t.(&@meta_pagination)
32
+ }
33
+ #=> {
34
+ # "users": [
35
+ # {
36
+ # "id": 1,
37
+ # "age": 21,
38
+ # "full_name": "John Smith",
39
+ # "avatar": {
40
+ # "image_url": "http://example.com/john.png"
41
+ # }
42
+ # },
43
+ # {
44
+ # "id": 2,
45
+ # "age": 37,
46
+ # "full_name": "William Northington",
47
+ # "avatar": {
48
+ # "image_url": "http://example.com/william.png"
49
+ # }
50
+ # },
51
+ # {
52
+ # "id": 3,
53
+ # "age": 29,
54
+ # "full_name": "Samuel Miller",
55
+ # "avatar": {
56
+ # "image_url": "http://example.com/samuel.png"
57
+ # }
58
+ # }
59
+ # ],
60
+ # "meta": {
61
+ # "total_pages": 1,
62
+ # "current_page": 1
63
+ # }
64
+ # }
65
+ ```
66
+
67
+
68
+ Usage
69
+ -----
70
+
71
+ Make sure to add the gem to your Gemfile.
72
+
73
+ ```ruby
74
+ gem 'neo_json'
75
+ ```
76
+
77
+ Start writing object:
78
+
79
+ ```ruby
80
+ Jsonity.build { |t|
81
+ # ...
82
+ }
83
+ ```
84
+
85
+ ### Object assignment
86
+
87
+ To declare the data object for use:
88
+
89
+ ```ruby
90
+ t <= @user
91
+ ```
92
+
93
+ ### Attribute nodes
94
+
95
+ Basic usage of defining simple attributes:
96
+
97
+ ```ruby
98
+ t.id # @user.id
99
+ t.age # @user.age
100
+ ```
101
+
102
+ Or you can use custom attributes in flexible ways:
103
+
104
+ ```ruby
105
+ t.full_name { |u| [u.first_name, u.last_name].join ' ' } # u = @user
106
+ t.russian_roulette { rand(1..10) } # block parameter isn't required
107
+ t.with_object(Time) { |t| t.now } # now, t = Time
108
+ t.seventeen 17 # block can be omitted
109
+ ```
110
+
111
+ Aliased attributes works well as you expected:
112
+
113
+ ```ruby
114
+ # show `id` as `my_id`
115
+ t.my_id &:id
116
+ ```
117
+
118
+ ### Automatic attributes inclusion
119
+
120
+ If you set `attr_json` in any class, the specified attributes will automatically be included:
121
+
122
+ ```ruby
123
+ class Sample < Struct.new(:id, :foo)
124
+ attr_json :id, :foo
125
+ end
126
+
127
+ @sample = Sample.new 123, 'foo!'
128
+
129
+ Jsonity.build { |t|
130
+ t.sample!(@sample) { |t|
131
+ # leave empty inside
132
+ }
133
+ }
134
+ #=> {
135
+ # "sample": {
136
+ # "id": 123,
137
+ # "foo": "foo!"
138
+ # }
139
+ # }
140
+ ```
141
+
142
+ ### Hash nodes
143
+
144
+ With name suffixed with `!`, nested object can be included:
145
+
146
+ ```ruby
147
+ t.user! { |user|
148
+ user.name # @user.name
149
+
150
+ user.avatar! { |avatar|
151
+ avatar.image_url # @user.avatar.image_url
152
+ avatar.width # @user.avatar.width
153
+ avatar.height # @user.avatar.height
154
+ }
155
+ }
156
+ ```
157
+
158
+ If `@user.avatar = nil`, the output will be like this:
159
+
160
+ ```javascipt
161
+ {
162
+ "user": {
163
+ "name": "John Smith",
164
+ "avatar": {
165
+ "image_url": null,
166
+ "width": null,
167
+ "height": null
168
+ }
169
+ }
170
+ }
171
+ ```
172
+
173
+ On the other hand, use `?` as suffix, the whole object become `null`:
174
+
175
+ ```ruby
176
+ t.user! { |user|
177
+ user.name
178
+
179
+ user.avatar? { |avatar|
180
+ avatar.image_url
181
+ avatar.width
182
+ avatar.height
183
+ }
184
+ }
185
+ ```
186
+
187
+ and the output will be:
188
+
189
+ ```javascipt
190
+ {
191
+ "user": {
192
+ "name": "John Smith",
193
+ "avatar": null
194
+ }
195
+ }
196
+ ```
197
+
198
+ Explicitly set an object to use inside a block:
199
+
200
+ ```ruby
201
+ t.home?(@user.hometown_address) { |home|
202
+ home.street # @user.hometown_address.street
203
+ home.zip
204
+ home.city
205
+ home.state
206
+ }
207
+ ```
208
+
209
+ Or blocks can inherit the parent object:
210
+
211
+ ```ruby
212
+ t.user! { |user|
213
+ t.my!(inherit: true) { |my|
214
+ my.name # @user.name
215
+ }
216
+ }
217
+ ```
218
+
219
+ ### Array nodes
220
+
221
+ Including a collection of objects, just use `t[]` and write the same syntax of hash node:
222
+
223
+ ```ruby
224
+ t[].friends! { |friend|
225
+ friend.name
226
+ }
227
+ ```
228
+
229
+ and the output JSON will be:
230
+
231
+ ```javascipt
232
+ {
233
+ "friends": [
234
+ {
235
+ name: "John Smith"
236
+ }
237
+ ]
238
+ }
239
+ ```
240
+
241
+ Similar to hash nodes in naming convention,
242
+ if `@user.friends = nil` nodes suffix with `!` will be an empty array `[]`, in contrast, some with `?` will be `null`.
243
+
244
+ Also passing the object or inheritance can be done in the same way as hash nodes.
245
+
246
+
247
+ ### Mixin / Scope
248
+
249
+ Since Jsonity aim to be simple and light, use plain `Proc` to fullfill functonality of mixin.
250
+
251
+ ```ruby
252
+ timestamp_mixin = ->(t) {
253
+ t.created_at
254
+ t.updated_at
255
+ }
256
+ ```
257
+
258
+ and then,
259
+
260
+ ```ruby
261
+ t.user! { |user|
262
+ user.(&timestamp_mixin)
263
+ }
264
+ ```
265
+
266
+ In case you might use different object in mixin, you can pass the object in the first argument:
267
+
268
+ ```ruby
269
+ t.(@other_user, &timestamps)
270
+ ```
271
+
272
+ So you take this functonality for scope:
273
+
274
+ ```ruby
275
+ t.(@other_user) { |other_user|
276
+ other_user.name
277
+ }
278
+ ```
279
+
280
+ #### Mixining nested object and merging
281
+
282
+ ```ruby
283
+ meta_pagination_mixin = ->(t) {
284
+ t.meta! { |meta|
285
+ meta.total_pages
286
+ meta.current_page
287
+ }
288
+ }
289
+ ```
290
+
291
+ and use this mixin like:
292
+
293
+ ```ruby
294
+ t[].people!(@people) { |person|
295
+ # ...
296
+ }
297
+
298
+ t.(@people, &meta_pagination_mixin)
299
+
300
+ t.meta! { |meta|
301
+ meta.total_count @people.count
302
+ }
303
+ ```
304
+
305
+ the output become:
306
+
307
+ ```javascript
308
+ {
309
+ "people": [
310
+ // ...
311
+ ],
312
+ "meta": {
313
+ "total_pages": 5,
314
+ "current_page": 1,
315
+ "total_count": 123
316
+ }
317
+ }
318
+ ```
319
+
320
+ Notice that two objects `meta!` got merged.
321
+
322
+ ### Conditions
323
+
324
+ Simply you can use `if` or `unless` statement inside the block:
325
+
326
+ ```
327
+ t[].people! { |person|
328
+ unless person.private_member?
329
+ person.name
330
+ person.age
331
+ end
332
+
333
+ person.cv if person.looking_for_job?
334
+ }
335
+ ```
336
+
337
+
338
+ With Rails
339
+ ----------
340
+
341
+ Helper method is available for rendering with Jsonity:
342
+
343
+ ```ruby
344
+ render_json(status: :ok) { |t|
345
+ # ...
346
+ }
347
+ ```
348
+
349
+
350
+ License
351
+ -------
352
+
353
+ This project is copyright by [Creasty](http://www.creasty.com), released under the MIT lisence.
354
+ See `LICENSE` file for details.
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require 'bundler/gem_tasks'
2
+
data/jsonity.gemspec ADDED
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'jsonity/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'jsonity'
8
+ spec.version = Jsonity::VERSION
9
+ spec.authors = ['Yuki Iwanaga']
10
+ spec.email = ['yuki@creasty.com']
11
+ spec.summary = 'The most natural language for building JSON in Ruby'
12
+ spec.description = 'The most natural language for building JSON in Ruby'
13
+ spec.homepage = 'https://github.com/creasty/jsonity'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_development_dependency 'bundler', '~> 1.6'
22
+ spec.add_development_dependency 'rake', '~> 10.3'
23
+ end
@@ -0,0 +1,38 @@
1
+ module Jsonity
2
+ module Attribute
3
+ module ClassMethods
4
+
5
+ ###
6
+ # Automatically export attributes to json
7
+ #
8
+ # @params {[String | Symbol]} *attrs
9
+ ###
10
+ def attr_json(*attrs)
11
+ @json_attributes ||= Set.new
12
+ @json_attributes |= attrs.map(&:to_s)
13
+ end
14
+
15
+ ###
16
+ # Get json attributes
17
+ #
18
+ # @return {[String]}
19
+ ###
20
+ def json_attributes
21
+ @json_attributes.to_a
22
+ end
23
+ end
24
+
25
+ module InstanceMethods
26
+
27
+ ###
28
+ # Get json attributes
29
+ #
30
+ # @return {[String]}
31
+ ###
32
+ def json_attributes
33
+ self.class.json_attributes
34
+ end
35
+
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,204 @@
1
+ require 'set'
2
+
3
+ module Jsonity
4
+ class Builder < BasicObject
5
+
6
+ ###
7
+ # Build Jsonity
8
+ #
9
+ # @params {any} object - [optional]
10
+ # @params {Hash | nil} content - [optional]
11
+ # @block
12
+ #
13
+ # @return {Hash} - json object
14
+ ###
15
+ def self.build(object = nil, content = nil, &block)
16
+ content = {} unless content.is_a?(::Hash)
17
+ builder = new object, content
18
+
19
+ if object.respond_to? :json_attributes
20
+ object.json_attributes.each { |a| builder.__send__ :attribute, a, nil }
21
+ end
22
+
23
+ builder.(&block)
24
+ builder.__send__ :content
25
+ end
26
+
27
+ ###
28
+ # Initializer
29
+ #
30
+ # @params {any} object
31
+ # @params {Hash | nil} content
32
+ ###
33
+ def initialize(object, content)
34
+ @object, @content = object, content
35
+ end
36
+
37
+ ###
38
+ # Set `obj` for the object
39
+ ###
40
+ def <=(obj)
41
+ @object = obj
42
+ end
43
+
44
+ ###
45
+ # Make array context
46
+ #
47
+ # @return {Jsonity::Builder} - `self`
48
+ ###
49
+ def []
50
+ @array = true
51
+ self
52
+ end
53
+
54
+ ###
55
+ # Mixin / Scoping
56
+ #
57
+ # @params {any} obj - [optional]
58
+ # @block
59
+ ###
60
+ def call(obj = nil, &block)
61
+ if obj
62
+ Builder.build obj, @content, &block
63
+ else
64
+ block_call block, self, @object
65
+ end
66
+ end
67
+
68
+
69
+ private
70
+
71
+ ###
72
+ # Handle ghost methods
73
+ ###
74
+ def method_missing(name, *args, &block)
75
+ name = name.to_s
76
+ is_object = name.match OBJECT_SUFFIX
77
+ name = name[0..-2] if is_object
78
+
79
+ options = args.last.is_a?(::Hash) ? args.pop : {}
80
+ options[:_object] = args[0]
81
+ options[:_nullable] = ('?' == is_object)
82
+
83
+ if @array
84
+ @array = false
85
+
86
+ if is_object
87
+ array name, options, &block
88
+ else
89
+ ::Kernel.raise UnexpectedNodeOnArrayError.new("Unexpected attribute node `#{name}`")
90
+ end
91
+ else
92
+ if is_object
93
+ hash name, options, &block
94
+ else
95
+ attribute name, options, &block
96
+ end
97
+ end
98
+
99
+ self
100
+ end
101
+
102
+ ###
103
+ # Getter for `@content`
104
+ #
105
+ # @return {Hash | nil}
106
+ ###
107
+ def content
108
+ @content
109
+ end
110
+
111
+ ###
112
+ # Create attribute node
113
+ #
114
+ # @params {String} name
115
+ # @params {Hash} options
116
+ # @block - [optional]
117
+ ###
118
+ def attribute(name, options, &block)
119
+ obj = get_object_for name, options
120
+
121
+ @content[name] = block ? block_call(block, obj || @object) : obj
122
+ end
123
+
124
+ ###
125
+ # Create hash node
126
+ #
127
+ # @params {String} name
128
+ # @params {Hash} options
129
+ # @block - [optional]
130
+ ###
131
+ def hash(name, options, &block)
132
+ obj = get_object_for name, options
133
+
134
+ if options[:_nullable] && !obj
135
+ @content[name] ||= nil
136
+ else
137
+ @content[name] = {} unless @content[name].is_a?(::Hash)
138
+ block ||= ->(t) {}
139
+ Builder.build obj, @content[name], &block
140
+ end
141
+ end
142
+
143
+ ###
144
+ # Create array node
145
+ #
146
+ # @params {String} name
147
+ # @params {Hash} options
148
+ # @block
149
+ ###
150
+ def array(name, options, &block)
151
+ obj = get_object_for name, options
152
+
153
+ is_array = obj && obj.class < ::Enumerable
154
+
155
+ if !is_array && options[:_nullable]
156
+ @content[name] ||= nil
157
+ return
158
+ end
159
+
160
+ @content[name] = [] unless @content[name].is_a?(::Array)
161
+ ary = @content[name]
162
+
163
+ if is_array
164
+ obj.each.with_index do |a, i|
165
+ # TODO: deferred build so that can be merged correctly with conditional nodes
166
+ ary[i] = Builder.build a, ary[i], &block
167
+ end
168
+ end
169
+ end
170
+
171
+ ###
172
+ # Get object
173
+ #
174
+ # @params {String} name
175
+ # @params {Hash} options - [optional]
176
+ #
177
+ # @return {any}
178
+ ###
179
+ def get_object_for(name, options = nil)
180
+ if options && options[:_object]
181
+ options[:_object]
182
+ elsif options && options[:inherit]
183
+ @object
184
+ elsif @object.respond_to? name
185
+ @object.public_send name
186
+ end
187
+ end
188
+
189
+ ###
190
+ # Arity safe proc call
191
+ #
192
+ # @params {Proc} block
193
+ # @params {[any]} *args
194
+ #
195
+ # @return {any}
196
+ ###
197
+ def block_call(block, *args)
198
+ ::Kernel.raise RequiredBlockError.new('No block') unless block
199
+
200
+ block.call *args.first(block.arity)
201
+ end
202
+
203
+ end
204
+ end
@@ -0,0 +1,8 @@
1
+ require 'jsonity/attribute'
2
+
3
+ class Object
4
+
5
+ extend Jsonity::Attribute::ClassMethods
6
+ include Jsonity::Attribute::InstanceMethods
7
+
8
+ end
@@ -0,0 +1,11 @@
1
+ module ActionController
2
+ class Base
3
+
4
+ def render_json(options = {}, &block)
5
+ json = Jsonity::Builder.build &block
6
+ options[:json] = json
7
+ render options
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ module Jsonity
2
+ VERSION = '1.0.0'
3
+ end
data/lib/jsonity.rb ADDED
@@ -0,0 +1,21 @@
1
+ require 'forwardable'
2
+ require 'jsonity/version'
3
+ require 'jsonity/builder'
4
+ require 'jsonity/core_ext'
5
+ require 'jsonity/rails' if defined? Rails
6
+
7
+ module Jsonity
8
+
9
+ extend Forwardable
10
+
11
+ OBJECT_SUFFIX = /[?!]$/
12
+
13
+ ## errors
14
+ class RequiredBlockError < StandardError; end
15
+ class UnexpectedNodeOnArrayError < StandardError; end
16
+
17
+ ## shortcut
18
+ def_delegator Builder, :build
19
+ module_function :build
20
+
21
+ end
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jsonity
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Yuki Iwanaga
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-09-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.3'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.3'
41
+ description: The most natural language for building JSON in Ruby
42
+ email:
43
+ - yuki@creasty.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".gitignore"
49
+ - Gemfile
50
+ - LICENSE
51
+ - README.md
52
+ - Rakefile
53
+ - jsonity.gemspec
54
+ - lib/jsonity.rb
55
+ - lib/jsonity/attribute.rb
56
+ - lib/jsonity/builder.rb
57
+ - lib/jsonity/core_ext.rb
58
+ - lib/jsonity/rails.rb
59
+ - lib/jsonity/version.rb
60
+ homepage: https://github.com/creasty/jsonity
61
+ licenses:
62
+ - MIT
63
+ metadata: {}
64
+ post_install_message:
65
+ rdoc_options: []
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ requirements: []
79
+ rubyforge_project:
80
+ rubygems_version: 2.2.2
81
+ signing_key:
82
+ specification_version: 4
83
+ summary: The most natural language for building JSON in Ruby
84
+ test_files: []