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/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.11.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



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

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)



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

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



257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
# File 'lib/turbostreamer.rb', line 257

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



24
25
26
# File 'lib/turbostreamer.rb', line 24

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

.get_encoder(mime, key) ⇒ Object



252
253
254
255
# File 'lib/turbostreamer.rb', line 252

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

.has_default_encoder_options?(encoder) ⇒ Boolean

Returns:

  • (Boolean)


248
249
250
# File 'lib/turbostreamer.rb', line 248

def self.has_default_encoder_options?(encoder)
  @@encoder_options.has_key?(encoder)
end

.key_format(*args) ⇒ Object

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



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

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

.key_formatter=(formatter) ⇒ Object



230
231
232
# File 'lib/turbostreamer.rb', line 230

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

.set_default_encoder(mime, encoder, default_options = nil) ⇒ Object



234
235
236
237
238
239
240
241
242
# File 'lib/turbostreamer.rb', line 234

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

  @@encoder_options[encoder] = default_options if default_options
end

.set_default_encoder_options(encoder, options) ⇒ Object



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

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

Instance Method Details

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



278
279
280
281
282
283
284
285
286
287
288
289
290
# File 'lib/turbostreamer.rb', line 278

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]


134
135
136
137
138
139
140
141
142
143
144
# File 'lib/turbostreamer.rb', line 134

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


313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
# File 'lib/turbostreamer.rb', line 313

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 }


99
100
101
102
103
104
105
# File 'lib/turbostreamer.rb', line 99

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



293
294
295
# File 'lib/turbostreamer.rb', line 293

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

#key!(key) ⇒ Object



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

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" }


221
222
223
# File 'lib/turbostreamer.rb', line 221

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

#merge!(hash_or_array) ⇒ Object



174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/turbostreamer.rb', line 174

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



59
60
61
62
63
# File 'lib/turbostreamer.rb', line 59

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 }


79
80
81
82
83
# File 'lib/turbostreamer.rb', line 79

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

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



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

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.



334
335
336
337
338
339
340
341
342
# File 'lib/turbostreamer.rb', line 334

def target!
  @encoder.flush

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

#value!(value) ⇒ Object



55
56
57
# File 'lib/turbostreamer.rb', line 55

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