Module: PushRoute::ClassMethods

Defined in:
lib/push_routes/push_route.rb

Instance Method Summary collapse

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method_sym, *arguments, &block) ⇒ Object

If it’s defined as an instance method and not a static method we’ll define a static method which wraps the functinality While this seems crazy, ActionMailer does this so there’s precident



80
81
82
83
84
85
86
# File 'lib/push_routes/push_route.rb', line 80

def method_missing(method_sym, *arguments, &block)
  if instance_trigger?(method_sym)
    new.send(method_sym, *arguments, &block)
  else
    super
  end
end

Instance Method Details

#add_belongs_to_trigger(action, model, type = :after_save) ⇒ Object

If you have a route like /room/:id/messages where Message belongs_to room you can use add_belongs_to_trigger :index, Message It will look at the url and try to add a trigger on Message save triggering the id as Message.room.id



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

def add_belongs_to_trigger(action, model, type = :after_save)
  enable_push_route action unless push_routes[action]
  url = push_routes[action]
  warn "Push Routes WARNING: Url #{url.inspect} contains too many params" if url.param_associations.count > 1
  warn "Push Routes WARNING: Url #{url.inspect} contains not enough notated params" if url.param_associations.count > 1
  sym = url.param_associations.first[1].singularize.to_sym
  if model.table_exists?
    if model&.send(:new)&.respond_to?(sym)
      add_trigger(action, model, type) { |object| {id: object.send(sym)&.id} }
    else
      warn "Push Routes WARNING: #{model} does not respond to #{sym} in belongs_to trigger"
    end
  else
    warn("Push Routes WARNING: no table exists for #{model}")
  end
end

#add_id_trigger(action, model, id_name, type = :after_save) ⇒ Object



162
163
164
# File 'lib/push_routes/push_route.rb', line 162

def add_id_trigger(action, model, id_name, type = :after_save)
  add_trigger(action, model, type) { |object| {id: object.send(id_name)} }
end

#add_show_trigger(action) ⇒ Object



187
188
189
190
191
192
193
# File 'lib/push_routes/push_route.rb', line 187

def add_show_trigger(action)
  if self.model
    add_id_trigger(action, self.model, :id)
  else
    warn "Push Routes WARNING: No model found for #{controller_path}"
  end
end

#add_trigger(action, model, type, trigger_function = nil) ⇒ Object

Sets a trigger for pushing updates function should return true to trigger update of the route; if the route requires params to resolve (i.e /users/:id/files) the params should be returned as a hash. Return false or nil to not trigger an update

Parameters:

  • the (symbol)

    action in this controller for the listener to trigger

  • model (ModelType)

    The type of model to listen to e.g. User, LabOrder

  • type (Symbol)

    Listener type, e.g. :after_update, see below for list

  • trigger_function (function) (defaults to: nil)

    Function to determine if an update should be triggered, either proc or reference



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/push_routes/push_route.rb', line 106

def add_trigger(action, model, type, trigger_function = nil)
  enable_push_route action unless push_routes[action]

  # Get get the callback passed in
  if (!trigger_function and block_given?)
    # Block only
    # Note Proc.new gets the passed in block without instantiating an extra proc
    # See http://mudge.name/2011/01/26/passing-blocks-in-ruby-without-block.html
    callback = Proc.new
  elsif trigger_function && trigger_function.is_a?(Proc)
    # Proc passed
    callback = trigger_function
  elsif trigger_function #&& !trigger_function.is_a?(Proc) implied here
    # Symbol passed
    trigger_symbols << trigger_function
    callback = lambda { |e| self.send(trigger_function, e) }
  else
    #default value
    callback = Proc.new { true }
  end

  if [:after_commit, :after_save, :after_update, :after_create, :after_destroy].include? type
    this = self
    func = lambda do |e|
      result = callback.call(e)
      if (result)
        result = [result] unless result.kind_of?(Array)
        result.each do |params|
          update_path = this.push_routes[action].notification_string(params)
          msg = {channel: update_path, type: "", data: "placeholder"}
          begin
            #TODO: Abstract away redis server as a transport
            #This try catch repetition is bad
            PushRoutes.redis.publish 'rt-change', msg.to_json
          rescue Redis::CannotConnectError => e
            warn "PushRoutes cannot reach the redis server"
          end
        end
      end
    end
    model.send(type, func)
  else
    raise ArgumentError.new("Invalid trigger type")
  end
end

#enable_push_route(action, route = nil) ⇒ Object

TODO: if not route already make, make it; set association between route(s) and action test method aliasing, perhaps can still do caching explicitly, or maybe just expiring caches properly; ignore caches for now

maybe just internal whisper pub sub with a routing adapter also pull out seperate class for each push route to store routes and triggers Maybe rails magic on for trigger function names



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/push_routes/push_route.rb', line 42

def enable_push_route(action, route = nil)
  matched_url = nil
  Rails.application.routes.routes.each do |e|
    if e.defaults[:controller] == self.controller_path && e.defaults[:action] == action.to_s
      unless matched_url
        matched_url = PushRoutes::PushRouteUrl.new(e)
      else
        raise ArgumentError.new("Duplicate route for push route controller")
      end
    end
  end

  #If a route exists and one is provided, make sure they are compatable
  if matched_url and route
    raise ArgumentError.new("Provided route does not match pre-existing route") unless (matched_url.matches(route))
    url = matched_url
  elsif route and !matched_url
    #There was no pre-existing route found and a route was specified
    # Add this route to the list of live routes
    url = PushRoutes::PushRouteUrl.new(route)
  elsif !route and matched_url
    #Matched one route only, we're good
    url = matched_url
  else
    #No route and no match
    raise ArgumentError.new("No route found and no route provided")
  end

  if push_routes[action]
    raise ArgumentError.new("Push Route enabled twice")
  else
    push_routes[action] = url
  end
end

#instance_trigger?(method_sym) ⇒ Boolean

Returns:

  • (Boolean)


93
94
95
# File 'lib/push_routes/push_route.rb', line 93

def instance_trigger?(method_sym)
  trigger_symbols.include?(method_sym) && instance_methods.include?(method_sym)
end

#modelObject

TODO: check for full class depth or shallow depth i.e. Api2::VitalsController



153
154
155
156
157
158
159
160
# File 'lib/push_routes/push_route.rb', line 153

def model
  #Note: const_defined is unrelialbe due to rails auto_loading, model might not be loaded
  begin
    Object.const_get(controller_path.classify)
  rescue
    nil
  end
end

#respond_to_missing?(method_sym, include_private = false) ⇒ Boolean

Overriding respond_to_missing to allow our method_missing override to behave properly

Returns:

  • (Boolean)


89
90
91
# File 'lib/push_routes/push_route.rb', line 89

def respond_to_missing?(method_sym, include_private = false)
  instance_trigger?(method_sym) || super
end