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.5.0'
DependencyTracker =
Class.new(dependency_tracker::ERBTracker)
@@default_encoders =
{}
@@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
# File 'lib/turbostreamer.rb', line 25

def initialize(options = {})
  @output_buffer = options[:output_buffer] || ::StringIO.new
  @encoder = if options[:encoder].is_a?(Symbol)
    TurboStreamer.get_encoder(options[:mime] || :json, options[:encoder])
  elsif options[:encoder].nil?
    TurboStreamer.default_encoder_for(options[:mime] || :json)
  else
    options[:encoder]
  end
  @encoder = @encoder.new(@output_buffer)

  @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)



179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/turbostreamer.rb', line 179

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



237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/turbostreamer.rb', line 237

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



232
233
234
235
# File 'lib/turbostreamer.rb', line 232

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.



216
217
218
# File 'lib/turbostreamer.rb', line 216

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

.key_formatter=(formatter) ⇒ Object



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

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

.set_default_encoder(mime, encoder) ⇒ Object



224
225
226
227
228
229
230
# File 'lib/turbostreamer.rb', line 224

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

Instance Method Details

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



258
259
260
261
262
263
264
265
266
267
268
269
270
# File 'lib/turbostreamer.rb', line 258

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]


124
125
126
127
128
129
130
131
132
133
134
# File 'lib/turbostreamer.rb', line 124

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


293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
# File 'lib/turbostreamer.rb', line 293

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 }


89
90
91
92
93
94
95
# File 'lib/turbostreamer.rb', line 89

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



273
274
275
# File 'lib/turbostreamer.rb', line 273

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

#key!(key) ⇒ Object



41
42
43
# File 'lib/turbostreamer.rb', line 41

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


211
212
213
# File 'lib/turbostreamer.rb', line 211

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

#merge!(hash_or_array) ⇒ Object



164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/turbostreamer.rb', line 164

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



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

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 }


69
70
71
72
73
# File 'lib/turbostreamer.rb', line 69

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

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



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/turbostreamer.rb', line 136

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.



314
315
316
317
318
319
320
321
322
# File 'lib/turbostreamer.rb', line 314

def target!
  @encoder.flush

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

#value!(value) ⇒ Object



45
46
47
# File 'lib/turbostreamer.rb', line 45

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