Class: TurboStreamer

Inherits:
Object
  • Object
show all
Defined in:
lib/turbostreamer/handler.rb,
lib/turbostreamer.rb,
lib/turbostreamer/errors.rb,
lib/turbostreamer/railtie.rb,
lib/turbostreamer/version.rb,
lib/turbostreamer/encoders/oj.rb,
lib/turbostreamer/key_formatter.rb,
lib/turbostreamer/encoders/wankel.rb,
lib/turbostreamer/dependency_tracker.rb

Overview

This module makes TurboStreamer work with Rails using the template handler API.

Direct Known Subclasses

Template

Defined Under Namespace

Modules: DependencyTrackerMethods, Errors Classes: Handler, KeyFormatter, OjEncoder, Railtie, Template, WankelEncoder

Constant Summary collapse

BLANK =
::Object.new
ENCODERS =
{
  json: {oj: 'Oj', wankel: 'Wankel'},
  msgpack: {msgpack: 'MessagePack'}
}
VERSION =
'1.8.0'
DependencyTracker =
Class.new(dependency_tracker::ERBTracker)
@@default_encoders =
{}
@@encoder_options =
Hash.new { |h, k| h[k] = {} }
@@key_formatter =
nil

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) {|_self| ... } ⇒ TurboStreamer

Returns a new instance of TurboStreamer.

Yields:

  • (_self)

Yield Parameters:

  • _self (TurboStreamer)

    the object that the method was called on



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/turbostreamer.rb', line 25

def initialize(options = {})
  @output_buffer = options[:output_buffer] || ::StringIO.new
  if options[:encoder].is_a?(Symbol)
    @encoder = TurboStreamer.get_encoder(options[:mime] || :json, options[:encoder])
    @encoder_options = @@encoder_options[options[:encoder]]
  elsif options[:encoder].nil?
    @encoder = TurboStreamer.default_encoder_for(options[:mime] || :json)
    if encoder_symbol = ENCODERS[options[:mime] || :json].find { |k, v| v == @encoder.name.delete_prefix('TurboStreamer::').delete_suffix('Encoder') }&.first
      @encoder_options = @@encoder_options[encoder_symbol]
    else
      @encoder_options = {}
    end
  else
    @encoder = options[:encoder]
    @encoder_options = {}
  end

  @encoder = @encoder.new(@output_buffer, @encoder_options)
  @key_formatter = options.fetch(:key_formatter){ @@key_formatter ? @@key_formatter.clone : nil }

  yield self if ::Kernel.block_given?
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missingObject (private)



186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/turbostreamer.rb', line 186

def set!(key, value = BLANK, *args, &block)
  key!(key)

  if block
    if !_blank?(value)
      # json.comments @post.comments { |comment| ... }
      # { "comments": [ { ... }, { ... } ] }
      _scope { array!(value, &block) }
    else
      # json.comments { ... }
      # { "comments": ... }
      _scope(&block)
    end
  elsif args.empty?
    # json.age 32
    # { "age": 32 }
    value!(value)
  elsif _eachable_arguments?(value, *args)
    # json.comments @post.comments, :content, :created_at
    # { "comments": [ { "content": "hello", "created_at": "..." }, { "content": "world", "created_at": "..." } ] }
    _scope{ array!(value, *args) }
  else
    # json.author @post.creator, :name, :email_address
    # { "author": { "name": "David", "email_address": "[email protected]" } }
    object!{ extract!(value, *args) }
  end
end

Class Method Details

.default_encoder_for(mime) ⇒ Object



249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
# File 'lib/turbostreamer.rb', line 249

def self.default_encoder_for(mime)
  if @@default_encoders[mime]
    @@default_encoders[mime]
  else
    ENCODERS[mime].to_a.find do |key, class_name|
      next if !const_defined?(class_name)
      return get_encoder(mime, key)
    end

    ENCODERS[mime].to_a.find do |key, class_name|
      begin
        return get_encoder(mime, key)
      rescue ::LoadError
        next
      end
    end

    raise ArgumentError, "Could not find an adapter to use"
  end
end

.encode(options = {}, &block) ⇒ Object



21
22
23
# File 'lib/turbostreamer.rb', line 21

def self.encode(options = {}, &block)
  new(options, &block).target!
end

.get_encoder(mime, key) ⇒ Object



244
245
246
247
# File 'lib/turbostreamer.rb', line 244

def self.get_encoder(mime, key)
  require "turbostreamer/encoders/#{key}"
  Object.const_get("TurboStreamer::#{ENCODERS[mime][key]}Encoder")
end

.key_format(*args) ⇒ Object

Same as the instance method key_format! except sets the default.



223
224
225
# File 'lib/turbostreamer.rb', line 223

def self.key_format(*args)
  @@key_formatter = KeyFormatter.new(*args)
end

.key_formatter=(formatter) ⇒ Object



227
228
229
# File 'lib/turbostreamer.rb', line 227

def self.key_formatter=(formatter)
  @@key_formatter = formatter
end

.set_default_encoder(mime, encoder, default_options = {}) ⇒ Object



231
232
233
234
235
236
237
238
# File 'lib/turbostreamer.rb', line 231

def self.set_default_encoder(mime, encoder, default_options={})
  if encoder.is_a?(Symbol)
    @@default_encoders[mime] = get_encoder(mime, encoder)
  else
    @@default_encoders[mime] = encoder
  end
  @@encoder_options[encoder] = default_options
end

.set_default_encoder_options(encoder, options) ⇒ Object



240
241
242
# File 'lib/turbostreamer.rb', line 240

def self.set_default_encoder_options(encoder, options)
  @@encoder_options[encoder] = options
end

Instance Method Details

#_extract_collection(collection, *attributes, &block) ⇒ Object



270
271
272
273
274
275
276
277
278
279
280
281
282
# File 'lib/turbostreamer.rb', line 270

def _extract_collection(collection, *attributes, &block)
  if collection.nil?
    # noop
  elsif block
    collection.each do |element|
      _scope{ yield element }
    end
  elsif attributes.any?
    collection.each { |element| pluck!(element, *attributes) }
  else
    collection.each { |element| value!(element) }
  end
end

#array!(collection = BLANK, *attributes, &block) ⇒ Object

Turns the current element into an array and iterates over the passed collection, adding each iteration as an element of the resulting array.

Example:

json.array!(@people) do |person|
  json.name person.name
  json.age calculate_age(person.birthday)
end

[ { "name": David", "age": 32 }, { "name": Jamie", "age": 31 } ]

It’s generally only needed to use this method for top-level arrays. If you have named arrays, you can do:

json.people(@people) do |person|
  json.name person.name
  json.age calculate_age(person.birthday)
end

{ "people": [ { "name": David", "age": 32 }, { "name": Jamie", "age": 31 } ] }

If you omit the block then you can set the top level array directly:

json.array! [1, 2, 3]

[1,2,3]


131
132
133
134
135
136
137
138
139
140
141
# File 'lib/turbostreamer.rb', line 131

def array!(collection = BLANK, *attributes, &block)
  @encoder.array_open

  if _blank?(collection)
    _scope(&block) if block
  else
    _extract_collection(collection, *attributes, &block)
  end

  @encoder.array_close
end

#child!(value = BLANK, *args, &block) ⇒ Object

Turns the current element into an array and yields a builder to add a hash.

Example:

json.comments do
  json.child! { json.content "hello" }
  json.child! { json.content "world" }
end

{ "comments": [ { "content": "hello" }, { "content": "world" } ]}

More commonly, you’d use the combined iterator, though:

json.comments(@post.comments) do |comment|
  json.content comment.formatted_content
end


305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
# File 'lib/turbostreamer.rb', line 305

def child!(value = BLANK, *args, &block)
  if block
    if _eachable_arguments?(value, *args)
      # json.child! comments { |c| ... }
      _scope { array!(value, &block) }
    else
      # json.child! { ... }
      # [...]
      _scope(&block)
    end
  elsif args.empty?
    value!(value)
  elsif _eachable_arguments?(value, *args)
    _scope{ array!(value, *args) }
  else
    object!{ extract!(value, *args) }
  end

end

#extract!(object, *attributes) ⇒ Object

Extracts the mentioned attributes or hash elements from the passed object and turns them into attributes of the JSON.

Example:

@person = Struct.new(:name, :age).new('David', 32)

or you can utilize a Hash

@person = { name: 'David', age: 32 }

json.extract! @person, :name, :age

{ "name": David", "age": 32 }, { "name": Jamie", "age": 31 }


96
97
98
99
100
101
102
# File 'lib/turbostreamer.rb', line 96

def extract!(object, *attributes)
  if ::Hash === object
    attributes.each{ |key| _set_value key, object.fetch(key) }
  else
    attributes.each{ |key| _set_value key, object.public_send(key) }
  end
end

#inject!(json_text) ⇒ Object

Inject a valid JSON string into the current



285
286
287
# File 'lib/turbostreamer.rb', line 285

def inject!(json_text)
  @encoder.inject(json_text)
end

#key!(key) ⇒ Object



48
49
50
# File 'lib/turbostreamer.rb', line 48

def key!(key)
  @encoder.key(_key(key))
end

#key_format!(*args) ⇒ Object

Specifies formatting to be applied to the key. Passing in a name of a function will cause that function to be called on the key. So :upcase will upper case the key. You can also pass in lambdas for more complex transformations.

Example:

json.key_format! :upcase
json.author do
  json.name "David"
  json.age 32
end

{ "AUTHOR": { "NAME": "David", "AGE": 32 } }

You can pass parameters to the method using a hash pair.

json.key_format! camelize: :lower
json.first_name "David"

{ "firstName": "David" }

Lambdas can also be used.

json.key_format! ->(key){ "_" + key }
json.first_name "David"

{ "_first_name": "David" }


218
219
220
# File 'lib/turbostreamer.rb', line 218

def key_format!(*args)
  @key_formatter = KeyFormatter.new(*args)
end

#merge!(hash_or_array) ⇒ Object



171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/turbostreamer.rb', line 171

def merge!(hash_or_array)
  if ::Array === hash_or_array
    hash_or_array.each do |array_element|
      value!(array_element)
    end
  elsif ::Hash === hash_or_array
    hash_or_array.each_pair do |key, value|
      key!(key)
      value!(value)
    end
  else
    raise Errors::MergeError.build(hash_or_array)
  end
end

#object!(&block) ⇒ Object



56
57
58
59
60
# File 'lib/turbostreamer.rb', line 56

def object!(&block)
  @encoder.map_open
  _scope { block.call } if block
  @encoder.map_close
end

#pluck!(object, *attributes) ⇒ Object

Extracts the mentioned attributes or hash elements from the passed object and turns them into a JSON object.

Example:

@person = Struct.new(:name, :age).new('David', 32)

or you can utilize a Hash

@person = { name: 'David', age: 32 }

json.pluck! @person, :name, :age

{ "name": David", "age": 32 }


76
77
78
79
80
# File 'lib/turbostreamer.rb', line 76

def pluck!(object, *attributes)
  object! do
    extract!(object, *attributes)
  end
end

#set!(key, value = BLANK, *args, &block) ⇒ Object Also known as: method_missing



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/turbostreamer.rb', line 143

def set!(key, value = BLANK, *args, &block)
  key!(key)

  if block
    if !_blank?(value)
      # json.comments @post.comments { |comment| ... }
      # { "comments": [ { ... }, { ... } ] }
      _scope { array!(value, &block) }
    else
      # json.comments { ... }
      # { "comments": ... }
      _scope(&block)
    end
  elsif args.empty?
    # json.age 32
    # { "age": 32 }
    value!(value)
  elsif _eachable_arguments?(value, *args)
    # json.comments @post.comments, :content, :created_at
    # { "comments": [ { "content": "hello", "created_at": "..." }, { "content": "world", "created_at": "..." } ] }
    _scope{ array!(value, *args) }
  else
    # json.author @post.creator, :name, :email_address
    # { "author": { "name": "David", "email_address": "[email protected]" } }
    object!{ extract!(value, *args) }
  end
end

#target!Object

Encodes the current builder as JSON.



326
327
328
329
330
331
332
333
334
# File 'lib/turbostreamer.rb', line 326

def target!
  @encoder.flush

  if @encoder.output.is_a?(::StringIO)
    @encoder.output.string
  else
    @encoder.output
  end
end

#value!(value) ⇒ Object



52
53
54
# File 'lib/turbostreamer.rb', line 52

def value!(value)
  @encoder.value(value)
end