jsonity 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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: []