json_factory 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4c49c73f5c727e2f83caad602c203bd3a4003961
4
- data.tar.gz: 1b589f2f5ac1cb970f65cf741d6b5b7fa6013b16
3
+ metadata.gz: e4514793bfcddf9c571232e5cf6bef2f4f0e3604
4
+ data.tar.gz: e6370cc5a01e97d48237e9ae60b6f23f6acd6d13
5
5
  SHA512:
6
- metadata.gz: 04a4f6a100f963652dd97bfc74bdfab4c7e8ddd9c414422cf43767a44f9557178a93344e94958f64c7d6a356d362f4801fb4e32bcd25cb25dc272f4eb943ddcd
7
- data.tar.gz: 77002b8c955a39e6cd19635ec0e124f745bcef1cf546976b980d464af860fc1e32e04df61a0d193a86007bac76e006317ac37d6224481dc1f214c788a8c18947
6
+ metadata.gz: 7c49593eaacc118a094cec4ec95bd5a15c359507f870d2b7911e0445e1c2aeb5f87b065d38cfa67741368f187657dd623ba935bade271d0d7ea455e0ab3e9ce2
7
+ data.tar.gz: f4ed8f8663d43b36e5f5a4c23af22f6a151470655e4766c27869a12ac1594d62db271aafd67b6853c5760586248485f13e146b7b513b75dc131f9a1431cc7562
data/.rubocop.yml CHANGED
@@ -12,6 +12,9 @@ Style/ClassVars:
12
12
  Exclude:
13
13
  - lib/json_factory/json_builder.rb
14
14
 
15
+ Metrics/ClassLength:
16
+ Max: 150
17
+
15
18
  Metrics/LineLength:
16
19
  Max: 140
17
20
  Exclude:
data/README.md CHANGED
@@ -22,29 +22,33 @@ Or install it yourself as:
22
22
 
23
23
  ## Usage
24
24
 
25
- | DSL Method | Description |
26
- | ------------- |:-------------------------------- |
27
- | json.object! | Create a json object |
28
- | json.array! | Create a json array |
29
- | json.member! | Create key value pair |
30
- | json.null! | Set object to "null" |
31
- | json.partial! | Load sub jfactory file |
32
- | json.cache! | Read and write from cache stores |
25
+ | DSL Method | Description |
26
+ | ---------- |:------------------------------------------------------------------- |
27
+ | value | Generates a JSON value. |
28
+ | object | Generates a JSON object structure. |
29
+ | member | Adds a key-value pair to a JSON object. |
30
+ | array | Generates a JSON array structure. |
31
+ | element | Adds a value to a JSON array. |
32
+ | partial | Loads the given partial and evaluates it using the local variables. |
33
+ | cache | Caches the given content under the specified key. |
33
34
 
34
35
  ##### Top level object JSON structure
35
36
 
36
37
  ```ruby
37
38
  factory = <<-RUBY
38
- json.object! do |json|
39
- json.object!(:data) do |json|
40
- json.member!(:id, object.id)
41
- json.member!(:name, object.name)
42
-
43
- json.array!(object.test_objects, :test_array) do |json, test_object|
44
- json.member!(:id, test_object.id)
45
- json.member!(:name, test_object.name)
39
+ json.object do
40
+ json.member :data do
41
+ json.object do
42
+ json.member :id, object.id
43
+ json.member :name, object.name
44
+ json.member :test_array do
45
+ json.object_array(object.test_objects) do |test_object|
46
+ json.member :id, test_object.id)
47
+ json.member :name, test_object.name
48
+ end
49
+ end
46
50
  end
47
- end
51
+ end
48
52
  end
49
53
  RUBY
50
54
 
@@ -53,10 +57,7 @@ test_object_1 = OpenStruct.new(id: '001', name: 'TestObject2')
53
57
  test_object_2 = OpenStruct.new(id: '002', name: 'TestObject3')
54
58
  test_object = OpenStruct.new(id: '1', name: 'TestObject1', test_objects: [test_object_1, test_object_2])
55
59
 
56
- # create context object
57
- context = JSONFactory::Context.new(object: test_object)
58
-
59
- puts JSONFactory::JSONBuilder.new(factory, context).build
60
+ puts JSONFactory.build(factory, object: test_object)
60
61
  ```
61
62
 
62
63
  ```json
@@ -76,9 +77,9 @@ puts JSONFactory::JSONBuilder.new(factory, context).build
76
77
 
77
78
  ```ruby
78
79
  factory = <<-RUBY
79
- json.array! objects do |json, test_object|
80
- json.member!(:id, test_object.id)
81
- json.member!(:name, test_object.name)
80
+ json.object_array objects do |test_object|
81
+ json.member :id, test_object.id
82
+ json.member :name, test_object.name
82
83
  end
83
84
  RUBY
84
85
 
@@ -86,10 +87,7 @@ RUBY
86
87
  test_object_1 = OpenStruct.new(id: '001', name: 'TestObject2')
87
88
  test_object_2 = OpenStruct.new(id: '002', name: 'TestObject3')
88
89
 
89
- # create context object
90
- context = JSONFactory::Context.new(objects: [test_object_1, test_object_2])
91
-
92
- puts JSONFactory::JSONBuilder.new(factory, context).build
90
+ puts JSONFactory.build(factory, objects: [test_object_1, test_object_2])
93
91
  ```
94
92
 
95
93
  ```json
@@ -103,32 +101,29 @@ puts JSONFactory::JSONBuilder.new(factory, context).build
103
101
 
104
102
  ```ruby
105
103
  # tmp/test.jfactory
106
- json.member!(:id, test_object.id)
107
- json.member!(:name, test_object.name)
104
+ json.member :id, test_object.id
105
+ json.member :name, test_object.name
108
106
  ```
109
107
 
110
108
  ```ruby
111
109
  # test data
112
110
  test_object = OpenStruct.new(id: '1', name: 'TestObject1')
113
111
 
114
- # create context object
115
- context = JSONFactory::Context.new(object: test_object)
116
-
117
- puts JSONFactory::JSONBuilder.load_factory_file('tmp/test.jfactory', context).build # => { "id": 1, name: "TestObject1" }
112
+ puts JSONFactory.build('tmp/test.jfactory', object: test_object).build # => { "id": 1, name: "TestObject1" }
118
113
  ```
119
114
 
120
115
  ##### Load partials
121
116
 
122
117
  ```ruby
123
118
  # tmp/_test_partial.jfactory
124
- json.member!(:id, test_object.id)
125
- json.member!(:name, test_object.name)
119
+ json.member :id, test_object.id
120
+ json.member :name, test_object.name
126
121
  ```
127
122
 
128
123
  ```ruby
129
124
  # tmp/test.jfactory
130
- json.object! do |json|
131
- json.partial!('tmp/test_partial', test_object: object)
125
+ json.object do
126
+ json.partial 'tmp/test_partial', test_object: object
132
127
  end
133
128
  ```
134
129
 
@@ -136,21 +131,20 @@ end
136
131
  # test data
137
132
  test_object = OpenStruct.new(id: '1', name: 'TestObject1')
138
133
 
139
- # create context object
140
- context = JSONFactory::Context.new(object: test_object)
141
-
142
- puts JSONFactory::JSONBuilder.load_factory_file('tmp/test.jfactory', context).build # => { "id": 1, name: "TestObject1" }
134
+ puts JSONFactory.build('tmp/test.jfactory', object: test_object).build # => { "id": 1, name: "TestObject1" }
143
135
  ```
144
136
 
145
137
  ##### Use cache stores
146
138
 
147
139
  ```ruby
148
140
  factory = <<-RUBY
149
- json.object! do |json|
150
- json.object!(:data) do |json|
151
- json.cache! 'test-cache-key' do |json|
152
- json.member!(:id, object.id)
153
- json.member!(:name, object.name)
141
+ json.object do
142
+ json.member :data do
143
+ json.object do
144
+ json.cache 'test-cache-key' do
145
+ json.member :id, object.id
146
+ json.member :name, object.name
147
+ end
154
148
  end
155
149
  end
156
150
  end
@@ -159,12 +153,9 @@ RUBY
159
153
  # test data
160
154
  test_object = OpenStruct.new(id: '1', name: 'TestObject1')
161
155
 
162
- # create context object
163
- context = JSONFactory::Context.new(object: test_object)
156
+ JSONFactory::Cache.instance.store = ActiveSupport::Cache::MemoryStore.new
164
157
 
165
- builder = JSONFactory::JSONBuilder.new(factory, context)
166
- builder.cache.store = ActiveSupport::Cache::MemoryStore.new
167
- puts builder.build # => { "data": { "id": "1", "name": "TestObject1" } }
158
+ puts JSONFactory.build(factory, object: test_object) # => { "data": { "id": "1", "name": "TestObject1" } }
168
159
  ```
169
160
 
170
161
  ## Development
@@ -1,10 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'bundler'
4
+ Bundler.setup
5
+
3
6
  require 'jbuilder'
4
7
  require 'benchmark'
5
8
  require 'forgery'
6
9
 
7
- require_relative '../lib/json_factory'
10
+ require 'json_factory'
8
11
 
9
12
  objects = []
10
13
  sub_array = []
@@ -28,9 +31,8 @@ end
28
31
  end
29
32
 
30
33
  # CURRENT RUNTIME WITH 1000 and 10 = 0.133410
31
- Benchmark.bmbm(15) do |x|
34
+ Benchmark.bmbm(10) do |x|
32
35
  x.report(:json_factory) do
33
- builder = JSONFactory::JSONBuilder.load_factory_file('fixtures/test.jfactory')
34
- builder.build(JSONFactory::Context.new(objects: objects))
36
+ JSONFactory.build('fixtures/test.jfactory', objects: objects)
35
37
  end
36
38
  end
@@ -1 +1 @@
1
- json.member!(:id, context.id)
1
+ json.member :id, id
@@ -1,8 +1,10 @@
1
- json.array! objects do |json, test_object|
2
- json.member!(:id, test_object.id)
3
- json.member!(:name, test_object.name)
4
- json.member!(:description, test_object.description)
5
- json.array!(test_object.test_array, :test_array) do |json, test_sub_object|
6
- json.partial! 'fixtures/test_partial', id: test_sub_object.id
1
+ json.object_array(objects) do |test_object|
2
+ json.member :id, test_object.id
3
+ json.member :name, test_object.name
4
+ json.member :description, test_object.description
5
+ json.member :test_array do
6
+ json.object_array(test_object.test_array) do |test_sub_object|
7
+ json.partial 'fixtures/test_partial', id: test_sub_object.id
8
+ end
7
9
  end
8
10
  end
@@ -1,19 +1,22 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative '../lib/json_factory'
2
4
 
3
5
  # test data
4
- test_object_1 = OpenStruct.new(id: '001', name: 'TestObject2')
5
- test_object_2 = OpenStruct.new(id: '002', name: 'TestObject3')
6
- test_object = OpenStruct.new(id: '1', name: 'TestObject1', description: 'Test2', test_objects: [test_object_1, test_object_2])
6
+ test_object1 = OpenStruct.new(id: '001', name: 'TestObject2')
7
+ test_object2 = OpenStruct.new(id: '002', name: 'TestObject3')
8
+ test_object = OpenStruct.new(id: '1', name: 'TestObject1', description: 'Test2', test_objects: [test_object1, test_object2])
7
9
 
8
10
  factory = <<-RUBY
9
- json.object! do |json|
10
- json.object!(:data) do |json|
11
- json.member!(:id, object.id)
12
- json.member!(:name, object.name)
13
-
14
- json.array!(object.test_objects, :test_array) do |json, test_object|
15
- json.member!(:id, test_object.id)
16
- json.member!(:name, test_object.name)
11
+ object! do
12
+ object!(:data) do
13
+ member!(:id, object.id)
14
+ member!(:name, object.name)
15
+ member!(:test_array) do
16
+ array!(object.test_objects) do |test_object|
17
+ member!(:id, test_object.id)
18
+ member!(:name, test_object.name)
19
+ end
17
20
  end
18
21
  end
19
22
  end
@@ -24,15 +27,13 @@ context = JSONFactory::Context.new(object: test_object)
24
27
 
25
28
  puts JSONFactory::JSONBuilder.new(factory, context).build
26
29
 
27
-
28
-
29
30
  factory = <<-RUBY
30
- json.array! objects do |json, test_object|
31
- json.member!(:id, test_object.id)
32
- json.member!(:name, test_object.name)
31
+ array! objects do |test_object|
32
+ member!(:id, test_object.id)
33
+ member!(:name, test_object.name)
33
34
  end
34
35
  RUBY
35
36
  # create context object
36
- context = JSONFactory::Context.new(objects: [test_object_1, test_object_2])
37
+ context = JSONFactory::Context.new(objects: [test_object1, test_object2])
37
38
 
38
39
  puts JSONFactory::JSONBuilder.new(factory, context).build
data/json_factory.gemspec CHANGED
@@ -7,11 +7,9 @@ require 'json_factory/version'
7
7
  Gem::Specification.new do |spec|
8
8
  spec.name = 'json_factory'
9
9
  spec.version = JSONFactory::VERSION
10
- spec.authors = ['Alexander Klaiber']
11
- spec.email = ['alex.klaiber@gmail.com']
10
+ spec.author = 'Alexander Klaiber'
12
11
 
13
- spec.summary = 'JsonFactory is a Easy DSL to create JSON structures with the development focus on performance.'
14
- spec.description = 'JsonFactory is a Easy DSL to create JSON structures with the development focus on performance.'
12
+ spec.summary = 'JsonFactory is a easy DSL to create JSON structures with a development focus on performance.'
15
13
  spec.homepage = 'https://github.com/aklaiber/json_factory'
16
14
  spec.license = 'MIT'
17
15
 
@@ -19,7 +17,7 @@ Gem::Specification.new do |spec|
19
17
  spec.require_paths = ['lib']
20
18
 
21
19
  spec.add_runtime_dependency 'activesupport', '>= 5.1.0'
22
- spec.add_runtime_dependency 'oj'
20
+ spec.add_runtime_dependency 'json'
23
21
  spec.add_runtime_dependency 'redis-activesupport', '>= 5.0.0'
24
22
 
25
23
  spec.add_development_dependency 'bundler'
data/lib/json_factory.rb CHANGED
@@ -1,21 +1,34 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'oj'
4
-
5
3
  require 'active_support'
6
4
  require 'active_support/cache/redis_store'
7
5
 
6
+ require 'json_factory/railtie' if defined?(Rails)
7
+
8
8
  require_relative 'json_factory/version'
9
- require_relative 'json_factory/context'
9
+ require_relative 'json_factory/configuration'
10
+ require_relative 'json_factory/errors'
11
+ require_relative 'json_factory/state'
12
+ require_relative 'json_factory/converter'
13
+ require_relative 'json_factory/dsl'
14
+ require_relative 'json_factory/dsl/object_array'
15
+ require_relative 'json_factory/template_store'
10
16
  require_relative 'json_factory/json_builder'
11
- require_relative 'json_factory/json_object'
17
+ require_relative 'json_factory/builder'
12
18
 
13
19
  module JSONFactory
14
- autoload :Cache, 'json_factory/cache.rb'
15
- autoload :CacheError, 'json_factory/cache_error.rb'
16
- autoload :CacheStoreProxy, 'json_factory/cache_store_proxy.rb'
17
- autoload :BaseStoreProxy, 'json_factory/cache_store_proxy/base_store_proxy.rb'
18
- autoload :RedisStoreProxy, 'json_factory/cache_store_proxy/redis_store_proxy.rb'
19
- autoload :MemoryStoreProxy, 'json_factory/cache_store_proxy/memory_store_proxy.rb'
20
- autoload :FileStoreProxy, 'json_factory/cache_store_proxy/file_store_proxy.rb'
20
+ autoload :Cache, 'json_factory/cache'
21
+ autoload :TemplateStore, 'json_factory/template_store.rb'
22
+
23
+ def self.build(template, local_variables = {})
24
+ Builder.new(template, local_variables).build
25
+ end
26
+
27
+ def self.configure
28
+ if block_given?
29
+ yield Configuration.instance
30
+ else
31
+ Configuration.instance
32
+ end
33
+ end
21
34
  end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSONFactory
4
+ class Builder
5
+ def initialize(template, local_variables = {})
6
+ @io = StringIO.new
7
+ @template = template
8
+ @local_variables = local_variables
9
+ end
10
+
11
+ def context
12
+ @local_variables
13
+ end
14
+
15
+ def build
16
+ json_builder = JSONBuilder.new(@io)
17
+ if File.exist?(@template)
18
+ json_builder.render_template(@template, @local_variables)
19
+ else
20
+ json_builder.render_string(@template, @local_variables)
21
+ end
22
+ @io.string
23
+ end
24
+ end
25
+ end
@@ -3,24 +3,18 @@
3
3
  module JSONFactory
4
4
  class Cache
5
5
  include Singleton
6
-
7
- attr_accessor :prefix
6
+ attr_accessor :store, :prefix
8
7
 
9
8
  def initialize
10
9
  @prefix = 'json_factory'
11
-
12
- self.store = ::Rails.cache if defined?(::Rails.cache)
13
10
  end
14
11
 
15
- attr_reader :store
16
- alias store_proxy store
17
-
18
- def store=(store)
19
- @store = CacheStoreProxy.build(store)
12
+ def transform_key(key)
13
+ [prefix, key].compact.join(':')
20
14
  end
21
15
 
22
- delegate :read, to: :store
23
- delegate :write, to: :store
24
- delegate :delete, to: :store
16
+ def fetch(key, options = nil, &block)
17
+ store.fetch(transform_key(key), options, &block)
18
+ end
25
19
  end
26
20
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'singleton'
4
+
5
+ module JSONFactory
6
+ class Configuration
7
+ include Singleton
8
+
9
+ attr_reader :helpers
10
+
11
+ def initialize
12
+ @helpers = []
13
+ end
14
+
15
+ def include_helper(mod)
16
+ @helpers.push(mod)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module JSONFactory
6
+ module Converter
7
+ def self.json_key(object)
8
+ json_value(object.to_s)
9
+ end
10
+
11
+ def self.json_value(object)
12
+ raise "don't know how to convert #{object.inspect} (#{object.class})" unless object.respond_to?(:to_json)
13
+ object.to_json
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,196 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSONFactory
4
+ class DSL
5
+ def self.check_arity(argc, expected)
6
+ return if expected === argc # rubocop:disable Style/CaseEquality
7
+ raise ArgumentError, "wrong number of arguments (given #{argc}, expected #{expected})"
8
+ end
9
+
10
+ def initialize(builder)
11
+ @builder = builder
12
+ end
13
+
14
+ # :call-seq:
15
+ # json.value(value) -> nil
16
+ #
17
+ # Generates a JSON value.
18
+ #
19
+ # json.value 1 # generates: 1
20
+ # json.value nil # generates: null
21
+ # json.value :foo # generates: "foo"
22
+ def value(value)
23
+ warn 'given block not used' if block_given?
24
+ @builder.value(value)
25
+ end
26
+
27
+ # :call-seq:
28
+ # json.array -> nil
29
+ # json.array { block } -> nil
30
+ #
31
+ # Generates a JSON array structure.
32
+ #
33
+ # The block is evaluated in order to add element to the array.
34
+ #
35
+ # If no block is given, an empty array is generated.
36
+ #
37
+ # json.array
38
+ # # generates: []
39
+ #
40
+ # json.array do
41
+ # json.element 1
42
+ # json.element 2
43
+ # end
44
+ # # generates: [1,2]
45
+ def array(&block)
46
+ @builder.array(&block)
47
+ end
48
+
49
+ # :call-seq:
50
+ # json.element(value) -> nil
51
+ # json.element { block } -> nil
52
+ #
53
+ # Adds a value to a JSON array. Results in an exception if called outside an
54
+ # array.
55
+ #
56
+ # If an argument is given, it is used as the element's value.
57
+ #
58
+ # If a block is given, it is evaluated in order to add nested structures.
59
+ #
60
+ # json.array do
61
+ # json.element 1
62
+ # json.element 2
63
+ # json.element do
64
+ # json.array do
65
+ # json.element 3
66
+ # json.element 4
67
+ # end
68
+ # end
69
+ # end
70
+ # # generates: [1,2,[3,4]]
71
+ def element(*args)
72
+ if block_given?
73
+ DSL.check_arity(args.length, 0..1)
74
+ warn 'block supersedes value argument' if args.length == 1 && block_given?
75
+ @builder.element { yield }
76
+ else
77
+ DSL.check_arity(args.length, 1)
78
+ @builder.element(*args)
79
+ end
80
+ end
81
+
82
+ # :call-seq:
83
+ # json.object -> nil
84
+ # json.object { block } -> nil
85
+ #
86
+ # Generates a JSON object structure.
87
+ #
88
+ # The block is evaluated in order to add key-value pairs to the object.
89
+ #
90
+ # If no block is given, an empty object is generated.
91
+ #
92
+ # json.object
93
+ # # generates: {}
94
+ #
95
+ # json.object do
96
+ # json.member :foo, 1
97
+ # json.member :bar, 2
98
+ # end
99
+ # # generates: {"foo":1,"bar":2}
100
+ def object
101
+ if block_given?
102
+ @builder.object { yield }
103
+ else
104
+ @builder.object
105
+ end
106
+ end
107
+
108
+ # :call-seq:
109
+ # json.member(key, value) -> nil
110
+ # json.member(key) { block } -> nil
111
+ #
112
+ # Adds a key-value pair to a JSON object. Results in an exception if called
113
+ # outside an object.
114
+ #
115
+ # The first argument is used as the key. If a second argument is given, it
116
+ # is used as the member's value.
117
+ #
118
+ # If a block is given, it is evaluated in order to add nested structures.
119
+ #
120
+ # json.object do
121
+ # json.member :foo, 1
122
+ # json.member :bar, 2
123
+ # json.member :baz do
124
+ # json.array do
125
+ # json.element 3
126
+ # json.element 4
127
+ # end
128
+ # end
129
+ # end
130
+ # # generates: {"foo":1,"bar":2,"baz":[3,4]}
131
+ def member(*args)
132
+ if block_given?
133
+ DSL.check_arity(args.length, 1..2)
134
+ warn 'block supersedes value argument' if args.length == 2 && block_given?
135
+ @builder.member(*args) { yield }
136
+ else
137
+ DSL.check_arity(args.length, 2)
138
+ @builder.member(*args)
139
+ end
140
+ end
141
+
142
+ # :call-seq:
143
+ # json.cache(key) { block } -> nil
144
+ #
145
+ # Caches the given content under the specified key.
146
+ #
147
+ # If a cache entry is found, it is added to the output. Otherwise, the given
148
+ # block is evaluated and the result is stored in the cache.
149
+ #
150
+ # json.object do
151
+ # json.member :foo, 1
152
+ # cache 'test-key' do
153
+ # json.member :bar, 2 # will be stored in the cache
154
+ # end
155
+ # cache 'test-key' do
156
+ # json.member :baz, 3 # will be ignored
157
+ # end
158
+ # end
159
+ # # generates: {"foo":1,"bar":2,"bar":2}
160
+ #
161
+ # When caching object members or array elements, commas are inserted as
162
+ # needed. However, no further checks are performed, so improper use may
163
+ # result in invalid JSON, for example by adding cached object members to an
164
+ # array.
165
+ def cache(key)
166
+ @builder.cache(key) { yield }
167
+ end
168
+
169
+ # :call-seq:
170
+ # json.partial(file, local_variables = {})
171
+ #
172
+ # Loads the given partial and evaluates it using the local variables.
173
+ #
174
+ # # simple.jfactory
175
+ # json.object do
176
+ # json.member :name, name
177
+ # end
178
+ #
179
+ # json.array do
180
+ # json.element do
181
+ # json.partial 'simple.jfactory', name: 'foo'
182
+ # end
183
+ # json.element do
184
+ # json.partial 'simple.jfactory', name: 'bar'
185
+ # end
186
+ # end
187
+ # # generates: [{"name":"foo"},{"name":"bar"}]
188
+ #
189
+ # Partial files are loaded only once to minimize file access.
190
+ def partial(file, local_variables = {})
191
+ path = Pathname.new(File.expand_path(file))
192
+ path = "#{path.dirname}/_#{path.basename}.jfactory" if path.extname.empty?
193
+ @builder.partial(path.to_s, local_variables)
194
+ end
195
+ end
196
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSONFactory
4
+ class DSL
5
+ # Helper method to generate an array of objects.
6
+ #
7
+ # json.object_array([1,2,3]) do |id|
8
+ # json.member :id, id
9
+ # end
10
+ # # generates: [{"id":1},{"id":2},{"id":2}]
11
+ #
12
+ # The above is equivalent to:
13
+ #
14
+ # json.array do
15
+ # [1,2,3].each do |id|
16
+ # json.object do
17
+ # json.member :id, id
18
+ # end
19
+ # end
20
+ # end
21
+ # # generates: [{"id":1},{"id":2},{"id":2}]
22
+ def object_array(collection)
23
+ array do
24
+ collection.each do |*values|
25
+ element do
26
+ object do
27
+ yield(*values)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSONFactory
4
+ class BuilderError < StandardError
5
+ end
6
+
7
+ class TypeNotAllowedError < BuilderError
8
+ end
9
+
10
+ class EmptyValueError < BuilderError
11
+ end
12
+ end
@@ -2,30 +2,161 @@
2
2
 
3
3
  module JSONFactory
4
4
  class JSONBuilder
5
- attr_accessor :output
6
- attr_reader :stream, :factory, :cache
5
+ BUILDER_VARIABLE_NAME = :json
7
6
 
8
- def self.load_factory_file(path, context = nil)
9
- raise "file format is invalid. #{path}" unless File.extname(path).eql?('.jfactory')
10
- raise "jfactory file #{path} not found" unless File.exist?(path)
11
- new(File.open(path).read, context)
12
- end
7
+ TOKEN_LEFT_SQUARE_BRACKET = '['
8
+ TOKEN_RIGHT_SQUARE_BRACKET = ']'
9
+ TOKEN_LEFT_CURLY_BRACKET = '{'
10
+ TOKEN_RIGHT_CURLY_BRACKET = '}'
11
+ TOKEN_COLON = ':'
12
+ TOKEN_COMMA = ','
13
13
 
14
- def initialize(factory, context = nil)
15
- @factory = factory
14
+ def initialize(io, type = :value)
15
+ @stack = [State.new(io, type)]
16
16
  @cache = Cache.instance
17
- @context = context
17
+ @template_store = TemplateStore.instance
18
+ end
19
+
20
+ def value(value = nil)
21
+ raise TypeNotAllowedError, 'Can only add value as a value' unless type == :value
22
+ raise TypeNotAllowedError, 'Cannot add multiple values' unless count.zero?
23
+ add_value(value)
24
+ increment_count
25
+ end
26
+
27
+ def array
28
+ raise TypeNotAllowedError, 'Can only add array as a value' unless type == :value
29
+ raise TypeNotAllowedError, 'Cannot add multiple values' unless count.zero?
30
+
31
+ io << TOKEN_LEFT_SQUARE_BRACKET
32
+ push_type(:array) { yield } if block_given?
33
+ io << TOKEN_RIGHT_SQUARE_BRACKET
34
+ increment_count
35
+ end
36
+
37
+ def element(value = nil)
38
+ raise TypeNotAllowedError, 'Can only add an element within an array' unless type == :array
39
+
40
+ add_separator
41
+ if block_given?
42
+ push_type(:value) { yield }
43
+ else
44
+ add_value(value)
45
+ end
46
+ increment_count
47
+ end
48
+
49
+ def object
50
+ raise TypeNotAllowedError, 'Can only add object as a value' unless type == :value
51
+ raise TypeNotAllowedError, 'Cannot add multiple values' unless count.zero?
52
+
53
+ io << TOKEN_LEFT_CURLY_BRACKET
54
+ push_type(:object) { yield } if block_given?
55
+ io << TOKEN_RIGHT_CURLY_BRACKET
56
+ increment_count
57
+ end
58
+
59
+ def member(key, value = nil)
60
+ raise TypeNotAllowedError, 'Can only add a member within an object' unless type == :object
61
+
62
+ add_separator
63
+ io << Converter.json_key(key)
64
+ io << TOKEN_COLON
65
+ if block_given?
66
+ push_type(:value) { yield }
67
+ else
68
+ add_value(value)
69
+ end
70
+ increment_count
71
+ end
72
+
73
+ def cache(key)
74
+ value = @cache.fetch(key) do
75
+ cache_io = StringIO.new
76
+ push_io(cache_io) { yield }
77
+ cache_io.string
78
+ end
79
+ raise EmptyValueError if value.empty?
80
+
81
+ add_separator
82
+ io << value
83
+ increment_count
84
+ end
85
+
86
+ def evaluate(string, local_variables, filename)
87
+ binding = jfactory
88
+ local_variables.each_pair do |key, value|
89
+ binding.local_variable_set(key, value)
90
+ end
91
+ binding.local_variable_set(BUILDER_VARIABLE_NAME, DSL.new(self))
92
+ eval(string, binding, filename.to_s) # rubocop:disable Security/Eval
93
+ end
94
+
95
+ def render_template(filename, local_variables)
96
+ template = @template_store.get(filename)
97
+ evaluate(template, local_variables, filename)
98
+ end
99
+ alias partial render_template
100
+
101
+ def render_string(string, local_variables)
102
+ evaluate(string, local_variables, '(inline)')
103
+ end
104
+
105
+ private
106
+
107
+ def add_value(value)
108
+ io << Converter.json_value(value)
18
109
  end
19
110
 
20
- def build
21
- @output = StringIO.new
22
- @stream = Oj::StreamWriter.new(@output, indent: 0)
111
+ def add_separator
112
+ io << TOKEN_COMMA unless count.zero?
113
+ end
114
+
115
+ def io
116
+ @stack.last.io
117
+ end
118
+
119
+ def type
120
+ @stack.last.type
121
+ end
122
+
123
+ def count
124
+ @stack.last.count
125
+ end
126
+
127
+ def increment_count
128
+ @stack.last.count += 1
129
+ end
130
+
131
+ def push_io(io)
132
+ @stack.push(State.new(io, type))
133
+ yield
134
+ @stack.pop
135
+ end
136
+
137
+ def push_type(type)
138
+ @stack.push(State.new(io, type))
139
+ yield
140
+ raise EmptyValueError if type == :value && count.zero?
141
+ @stack.pop
142
+ end
143
+ end
144
+ end
23
145
 
24
- JSONObject.new(self, @context).schema! { |json|
25
- json.instance_eval(@factory)
26
- }
146
+ JSONFactory::JSONBuilder.class_eval do
147
+ # Returns an empty evaluation context, similar to Ruby's main object.
148
+ def jfactory
149
+ Object.allocate.instance_eval do
150
+ class << self
151
+ JSONFactory.configure.helpers.each { |mod| include mod }
27
152
 
28
- @output.string.delete("\n").gsub('null}', 'null') if @output
153
+ def to_s
154
+ 'jfactory'
155
+ end
156
+ alias inspect to_s
157
+ end
158
+ return binding
29
159
  end
30
160
  end
161
+ private :jfactory
31
162
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSONFactory
4
+ class Railtie < Rails::Railtie
5
+ initializer 'json_factory.cache' do
6
+ Cache.instance.store = Rails.cache
7
+ end
8
+
9
+ initializer 'json_factory.jfactory_watcher' do |app|
10
+ jfactory_reloader = app.config.file_watcher.new(Dir.glob(::Rails.root.join('app/views/**/*.jfactory'))) do
11
+ TemplateStore.instance.clear
12
+ end
13
+
14
+ app.reloaders << jfactory_reloader
15
+
16
+ config.to_prepare do
17
+ jfactory_reloader.execute_if_updated
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSONFactory
4
+ class State
5
+ attr_accessor :io, :type, :count
6
+
7
+ def initialize(io, type, count = 0)
8
+ @io = io
9
+ @type = type
10
+ @count = count
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSONFactory
4
+ class TemplateStore
5
+ include Singleton
6
+
7
+ def initialize
8
+ @templates = {}
9
+ end
10
+
11
+ def get(path)
12
+ if @templates.key? path
13
+ @templates.fetch(path)
14
+ else
15
+ @templates.store(path, read_template(path))
16
+ end
17
+ end
18
+
19
+ def clear
20
+ @templates.clear
21
+ end
22
+
23
+ private
24
+
25
+ def read_template(path)
26
+ raise "file format is invalid. #{path}" unless File.extname(path).eql?('.jfactory')
27
+ raise "jfactory file #{path} not found" unless File.exist?(path)
28
+ File.read(path)
29
+ end
30
+ end
31
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JSONFactory
4
- VERSION = '0.3.0'
4
+ VERSION = '0.4.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: json_factory
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
  - Alexander Klaiber
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-12-08 00:00:00.000000000 Z
11
+ date: 2018-01-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -25,7 +25,7 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: 5.1.0
27
27
  - !ruby/object:Gem::Dependency
28
- name: oj
28
+ name: json
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
@@ -150,10 +150,8 @@ dependencies:
150
150
  - - ">="
151
151
  - !ruby/object:Gem::Version
152
152
  version: '0'
153
- description: JsonFactory is a Easy DSL to create JSON structures with the development
154
- focus on performance.
155
- email:
156
- - alex.klaiber@gmail.com
153
+ description:
154
+ email:
157
155
  executables: []
158
156
  extensions: []
159
157
  extra_rdoc_files: []
@@ -176,16 +174,17 @@ files:
176
174
  - examples/readme_examples.rb
177
175
  - json_factory.gemspec
178
176
  - lib/json_factory.rb
177
+ - lib/json_factory/builder.rb
179
178
  - lib/json_factory/cache.rb
180
- - lib/json_factory/cache_error.rb
181
- - lib/json_factory/cache_store_proxy.rb
182
- - lib/json_factory/cache_store_proxy/base_store_proxy.rb
183
- - lib/json_factory/cache_store_proxy/file_store_proxy.rb
184
- - lib/json_factory/cache_store_proxy/memory_store_proxy.rb
185
- - lib/json_factory/cache_store_proxy/redis_store_proxy.rb
186
- - lib/json_factory/context.rb
179
+ - lib/json_factory/configuration.rb
180
+ - lib/json_factory/converter.rb
181
+ - lib/json_factory/dsl.rb
182
+ - lib/json_factory/dsl/object_array.rb
183
+ - lib/json_factory/errors.rb
187
184
  - lib/json_factory/json_builder.rb
188
- - lib/json_factory/json_object.rb
185
+ - lib/json_factory/railtie.rb
186
+ - lib/json_factory/state.rb
187
+ - lib/json_factory/template_store.rb
189
188
  - lib/json_factory/version.rb
190
189
  homepage: https://github.com/aklaiber/json_factory
191
190
  licenses:
@@ -210,6 +209,6 @@ rubyforge_project:
210
209
  rubygems_version: 2.6.11
211
210
  signing_key:
212
211
  specification_version: 4
213
- summary: JsonFactory is a Easy DSL to create JSON structures with the development
214
- focus on performance.
212
+ summary: JsonFactory is a easy DSL to create JSON structures with a development focus
213
+ on performance.
215
214
  test_files: []
@@ -1,6 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module JSONFactory
4
- class CacheError < StandardError
5
- end
6
- end
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module JSONFactory
4
- module CacheStoreProxy
5
- PROXIES = [RedisStoreProxy, MemoryStoreProxy, FileStoreProxy].freeze
6
-
7
- def self.build(store)
8
- store_proxy = PROXIES.find { |proxy| proxy.handle?(store) }
9
- fail CacheError, "#{store.class} is a not supported cache" if store_proxy.nil?
10
- store_proxy.new(store)
11
- end
12
- end
13
- end
@@ -1,30 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module JSONFactory
4
- module CacheStoreProxy
5
- class BaseStoreProxy
6
- attr_reader :store
7
-
8
- def self.handle?(store)
9
- defined?(self::STORE_CLASS) && store.is_a?(self::STORE_CLASS)
10
- end
11
-
12
- def initialize(store, prefix = 'json_factory')
13
- @store = store
14
- @prefix = prefix
15
- end
16
-
17
- def read(key, options = nil)
18
- store.read("#{@prefix}:#{key}", options)
19
- end
20
-
21
- def write(key, value, options = nil)
22
- store.write("#{@prefix}:#{key}", value, options)
23
- end
24
-
25
- def delete(key, options = nil)
26
- store.delete("#{@prefix}:#{key}", options)
27
- end
28
- end
29
- end
30
- end
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module JSONFactory
4
- module CacheStoreProxy
5
- class FileStoreProxy < BaseStoreProxy
6
- STORE_CLASS = ActiveSupport::Cache::FileStore.freeze
7
- end
8
- end
9
- end
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module JSONFactory
4
- module CacheStoreProxy
5
- class MemoryStoreProxy < BaseStoreProxy
6
- STORE_CLASS = ActiveSupport::Cache::MemoryStore.freeze
7
- end
8
- end
9
- end
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module JSONFactory
4
- module CacheStoreProxy
5
- class RedisStoreProxy < BaseStoreProxy
6
- STORE_CLASS = ActiveSupport::Cache::RedisStore.freeze
7
- end
8
- end
9
- end
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module JSONFactory
4
- class Context
5
- def initialize(data = nil)
6
- @data = data
7
- end
8
-
9
- def add(key, value)
10
- @data[key] = value
11
- end
12
-
13
- def method_missing(method_name, *arguments, &block)
14
- return @data[method_name] if @data.is_a?(Hash) && @data.key?(method_name)
15
- super
16
- end
17
-
18
- def respond_to_missing?(method_name, include_private = false)
19
- (@data.is_a?(Hash) && @data.key?(method_name)) || super
20
- end
21
- end
22
- end
@@ -1,86 +0,0 @@
1
- module JSONFactory
2
- class JSONObject
3
- attr_reader :json_builder
4
- attr_accessor :context
5
-
6
- @@partials = {}
7
-
8
- def initialize(json_builder, context = nil)
9
- @json_builder = json_builder
10
- @context = context
11
- end
12
-
13
- def open_object
14
- json_builder.stream.push_object
15
- end
16
-
17
- def close_object
18
- json_builder.stream.pop
19
- end
20
-
21
- def schema!(object = nil)
22
- yield self, object
23
- self
24
- end
25
-
26
- def object!(key = nil, &block)
27
- json_object = JSONObject.new(json_builder)
28
-
29
- json_builder.stream.push_key(key.to_s) if key
30
-
31
- json_object.open_object
32
- json_object.schema!(&block)
33
- json_object.close_object
34
- end
35
-
36
- def array!(array, key = nil, &block)
37
- json_builder.stream.push_array(key ? key.to_s : nil)
38
- array.each do |object|
39
- builder = JSONObject.new(json_builder)
40
- builder.open_object
41
- builder.schema!(object, &block)
42
- builder.close_object
43
- end
44
- json_builder.stream.pop
45
- end
46
-
47
- def member!(key, value = nil)
48
- json_builder.stream.push_value(value, key.to_s)
49
- end
50
-
51
- def null!
52
- json_builder.output.seek(json_builder.output.pos - 1)
53
- json_builder.output.puts('null')
54
- end
55
-
56
- def cache!(cache_key, &block)
57
- json = json_builder.cache.read(cache_key) if cache_key && json_builder.cache
58
- return json_builder.output.puts(json) unless json.blank?
59
-
60
- start_cache_pos = json_builder.output.pos
61
- self.schema!(&block)
62
- end_cache_pos = json_builder.output.pos
63
-
64
- json_builder.cache.write(cache_key, json_builder.output.string[start_cache_pos..end_cache_pos])
65
- end
66
-
67
- def partial!(path, data = {})
68
- path = Pathname.new(path)
69
-
70
- if path.extname.eql?('.jfactory')
71
- @@partials[path] ||= File.open(path).read
72
- else
73
- @@partials[path] ||= File.open("#{path.dirname}/_#{path.basename}.jfactory").read
74
- end
75
-
76
- JSONObject.new(json_builder, JSONFactory::Context.new(data)).schema! do |json|
77
- json.instance_eval(@@partials[path])
78
- end
79
- end
80
-
81
- def method_missing(method_name, *arguments, &block)
82
- return context.send(method_name) if context.respond_to?(method_name)
83
- super
84
- end
85
- end
86
- end