Class: ReactiveManager

Inherits:
Object show all
Includes:
Events
Defined in:
lib/volt/reactive/reactive_value.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Events

#event_chain, #has_listeners?, #listeners, #on, #remove_listener, #trigger!, #trigger_by_scope!, #trigger_for_methods!

Constructor Details

#initialize(reactive_value, getter, setter = nil, scope = nil) ⇒ ReactiveManager

When created, ReactiveValue’s get a getter (a proc)



202
203
204
205
206
207
208
209
# File 'lib/volt/reactive/reactive_value.rb', line 202

def initialize(reactive_value, getter, setter=nil, scope=nil)
  @reactive_value = reactive_value
  @getter = getter
  @setter = setter
  @scope = scope

  @parents = []
end

Instance Attribute Details

#parentsObject (readonly)

Returns the value of attribute parents.



199
200
201
# File 'lib/volt/reactive/reactive_value.rb', line 199

def parents
  @parents
end

#scopeObject (readonly)

Returns the value of attribute scope.



199
200
201
# File 'lib/volt/reactive/reactive_value.rb', line 199

def scope
  @scope
end

Instance Method Details

#add_parent!(parent) ⇒ Object



410
411
412
413
# File 'lib/volt/reactive/reactive_value.rb', line 410

def add_parent!(parent)
  @parents << parent
  event_chain.add_object(parent)
end

#check_tag(method_name, tag_name, current_obj) ⇒ Object

Method calls can be tagged so the reactive value knows how to handle them. This lets you check the state of the tags.



337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
# File 'lib/volt/reactive/reactive_value.rb', line 337

def check_tag(method_name, tag_name, current_obj)
  if current_obj.respond_to?(:reactive_method_tag)
    tag = current_obj.reactive_method_tag(method_name, tag_name)

    unless tag
      # Get the tag from the all methods if its not directly specified
      tag = current_obj.reactive_method_tag(:__all_methods, tag_name)
    end

    # Evaluate now if its a proc
    tag = tag.call(method_name) if tag.class == ::Proc

    return tag
  end

  return nil
end

#cur(shallow = false, ignore_cache = false) ⇒ Object

Fetch the current value



244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
# File 'lib/volt/reactive/reactive_value.rb', line 244

def cur(shallow=false, ignore_cache=false)
  # Return from cache if it is cached
  if @cur_cache && !shallow && !ignore_cache
    # puts "From Cache: #{@cur_cache.inspect}"
    return @cur_cache
  end

  if @getter.class == ::Proc
    # Get the current value, capture any errors
    begin
      result = @getter.call
    rescue => e
      result = e
    end
  else
    # getter is just an object, return it
    result = @getter
  end

  if !shallow && result.reactive?
    # Unwrap any stored reactive values
    result = result.cur
  end

  # puts "CUR FOR: #{result.inspect}"

  return result
end

#cur=(val) ⇒ Object



310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
# File 'lib/volt/reactive/reactive_value.rb', line 310

def cur=(val)
  if @setter
    @setter.call(val)
    # update_followers
  elsif @scope == nil
    @getter = val
    @setter = nil

    # update_followers
    trigger!('changed')
  else
    raise "Value can not be updated"
  end

end

#deep_curObject

Returns a copy of the object with where all ReactiveValue’s are replaced with their current value. NOTE: Classes need to implement their own deep_cur method for this to work, it works out of the box with arrays and hashes.



330
331
332
# File 'lib/volt/reactive/reactive_value.rb', line 330

def deep_cur
  self.cur.deep_cur
end

#event_added(event, scope, first, first_for_event) ⇒ Object



228
229
230
231
232
233
# File 'lib/volt/reactive/reactive_value.rb', line 228

def event_added(event, scope, first, first_for_event)
  # When the first event is registered, we need to start listening on our current object
  # for it to publish events.

  update_followers if first
end

#event_removed(event, last, last_for_event) ⇒ Object



235
236
237
238
239
240
# File 'lib/volt/reactive/reactive_value.rb', line 235

def event_removed(event, last, last_for_event)
  # If no one is listening on the reactive value, then we don't need to listen on our
  # current object for events, because no one cares.

  remove_followers if last
end

#inspectObject



219
220
221
# File 'lib/volt/reactive/reactive_value.rb', line 219

def inspect
  "@<#{self.class.to_s}:#{reactive_object_id} #{cur.inspect}>"
end

#reactive?Boolean

Returns:



215
216
217
# File 'lib/volt/reactive/reactive_value.rb', line 215

def reactive?
  true
end

#reactive_object_idObject



223
224
225
# File 'lib/volt/reactive/reactive_value.rb', line 223

def reactive_object_id
  @reactive_object_id ||= rand(100000)
end

#reactive_valueObject



211
212
213
# File 'lib/volt/reactive/reactive_value.rb', line 211

def reactive_value
  @reactive_value
end

#remove_followersObject



297
298
299
300
301
302
303
304
305
306
307
308
# File 'lib/volt/reactive/reactive_value.rb', line 297

def remove_followers
  # puts "REMOVE FOLLOWERS: #{@cur_cache.inspect} on #{self.inspect}"
  # Remove from previous
  if @cur_cache
    @cur_cache = nil
  end

  if @cur_cache_chain_listener
    @cur_cache_chain_listener.remove
    @cur_cache_chain_listener = nil
  end
end

#remove_parent!(parent) ⇒ Object



415
416
417
418
# File 'lib/volt/reactive/reactive_value.rb', line 415

def remove_parent!(parent)
  @parents.delete(parent)
  event_chain.remove_object(parent)
end

#set_scope(new_scope) ⇒ Object



427
428
429
# File 'lib/volt/reactive/reactive_value.rb', line 427

def set_scope(new_scope)
  dup.scope!(new_scope)
end

#set_scope!(new_scope) ⇒ Object



421
422
423
424
425
# File 'lib/volt/reactive/reactive_value.rb', line 421

def set_scope!(new_scope)
  @scope = new_scope

  self
end

#setter!(setter = nil, &block) ⇒ Object

Sets the setter



432
433
434
# File 'lib/volt/reactive/reactive_value.rb', line 432

def setter!(setter=nil, &block)
  @setter = setter || block
end

#unwrap_if_pass_reactive(args, method_name, current_obj) ⇒ Object



355
356
357
358
359
360
361
# File 'lib/volt/reactive/reactive_value.rb', line 355

def unwrap_if_pass_reactive(args, method_name, current_obj)
  # Check to see if the method we're calling wants to receive reactive values.
  pass_reactive = check_tag(method_name, :pass_reactive, current_obj)

  # Unwrap arguments if the method doesn't want reactive values
  return pass_reactive ? args : args.map{|v| v.cur }
end

#update_followersObject



274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
# File 'lib/volt/reactive/reactive_value.rb', line 274

def update_followers
  if has_listeners?
    current_obj = cur(false, true)
    should_attach = current_obj.respond_to?(:on)
    # puts "SA #{should_attach} - #{current_obj.inspect}"

    if should_attach
      if !@cur_cache || current_obj.object_id != @cur_cache.object_id
        # puts "CHANGED FROM: #{@cur_cache.inspect} to #{current_obj.inspect} - #{current_obj.object_id} vs #{@cur_cache.object_id}"
        remove_followers

        # puts "SET TO: #{current_obj.inspect} on #{self.inspect}"
        @cur_cache_chain_listener = self.event_chain.add_object(current_obj)
      end
    else
      remove_followers
    end

    # Store current if we have listeners
    @cur_cache = current_obj
  end
end

#with(*args, &block) ⇒ Object

With returns a new reactive value dependent on any arguments passed in. If a block is passed in, the getter is the block its self, which will be passed the .cur and the .cur of any reactive arguments.



366
367
368
# File 'lib/volt/reactive/reactive_value.rb', line 366

def with(*args, &block)
  return with_and_options(args, &block)
end

#with_and_options(args, &block) ⇒ Object



370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
# File 'lib/volt/reactive/reactive_value.rb', line 370

def with_and_options(args, &block)
  getter = @getter
  setter = @setter
  scope = @scope

  if block
    # If a block was passed in, the getter now becomes a proc that calls
    # the passed in block with the right arguments.
    getter = ::Proc.new do
      # TODO: Calling cur every time
      current_val = self.cur

      if current_val.is_a?(Exception)
        current_val
      else
        block.call(current_val, args)
      end
    end

    # TODO: Make this work with custom setters
    setter = nil

    # Scope also gets set to nil, because now we should always retrigger this
    # method because we don't know enough about what methods its calling.
    scope = nil
  end

  new_val = ReactiveValue.new(getter, setter, scope)

  # Add the ReactiveValue we're building from
  new_val.reactive_manager.add_parent!(self)

  # Add any reactive arguments as parents
  args.select(&:reactive?).each do |arg|
    new_val.reactive_manager.add_parent!(arg.reactive_manager)
  end

  return new_val
end