Class: Volt::ModelController

Inherits:
Object
  • Object
show all
Includes:
CollectionHelpers, LifecycleCallbacks, ReactiveAccessors
Defined in:
lib/volt/controllers/model_controller.rb

Direct Known Subclasses

NoticesController

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from CollectionHelpers

#channel, #cookies, #flash, #local_store, #page, #params, #store, #tasks, #url, #url_for, #url_with

Methods included from LifecycleCallbacks

included, #run_callbacks, #stop_chain

Methods included from ReactiveAccessors

#__reactive_dependency_get, included

Constructor Details

#initialize(volt_app, *args) ⇒ ModelController

Returns a new instance of ModelController.



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/volt/controllers/model_controller.rb', line 166

def initialize(volt_app, *args)
  @volt_app = volt_app

  # Track that the initialize was called
  @__init_called = true

  default_model = self.class.default_model
  self.model = default_model if default_model

  if args[0]
    # Assign the first passed in argument to attrs
    self.attrs = args[0]

    # If a model attribute is passed in, we assign it directly
    self.model = attrs.locals[:model] if attrs.respond_to?(:model)
  end

end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method_name, *args, &block) ⇒ Object



252
253
254
255
256
257
258
259
260
# File 'lib/volt/controllers/model_controller.rb', line 252

def method_missing(method_name, *args, &block)
  model = self.model

  if model
    model.send(method_name, *args, &block)
  else
    super
  end
end

Instance Attribute Details

#attrsObject

Returns the value of attribute attrs.



164
165
166
# File 'lib/volt/controllers/model_controller.rb', line 164

def attrs
  @attrs
end

#sectionObject

The section is assigned a reference to a “DomSection” which has the dom for the controllers view.



23
24
25
# File 'lib/volt/controllers/model_controller.rb', line 23

def section
  @section
end

Class Method Details

.model(val) ⇒ Object



100
101
102
# File 'lib/volt/controllers/model_controller.rb', line 100

def self.model(val)
  self.default_model = val
end

.new(volt_app, *args, &block) ⇒ Object



147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/volt/controllers/model_controller.rb', line 147

def self.new(volt_app, *args, &block)
  inst = allocate

  # In MRI initialize is private for some reason, so call it with send
  inst.send(:initialize, volt_app, *args, &block)

  if inst.instance_variable_get('@__init_called')
    inst.instance_variable_set('@__init_called', nil)
  else
    # Initialize was not called, we should warn since this is probably not
    # the intended behavior.
    Volt.logger.warn("super should be called when creating a custom initialize on class #{inst.class.to_s}")
  end

  inst
end

Instance Method Details

#containerObject

Container returns the node that is parent to all nodes in the section.



29
30
31
32
# File 'lib/volt/controllers/model_controller.rb', line 29

def container
  check_section!('container')
  section.container_node
end

#controllerObject



200
201
202
# File 'lib/volt/controllers/model_controller.rb', line 200

def controller
  @controller ||= Model.new
end

#dom_nodesObject



34
35
36
37
# File 'lib/volt/controllers/model_controller.rb', line 34

def dom_nodes
  check_section!('dom_nodes')
  section.range
end

#first_elementObject

Walks the dom_nodes range until it finds an element. Typically this will be the container element without the whitespace text nodes.



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/volt/controllers/model_controller.rb', line 41

def first_element
  check_section!('first_element')
  range = dom_nodes
  nodes = `range.startContainer.childNodes`

  start_index = `range.startOffset`
  end_index = `range.endOffset`

  start_index.upto(end_index) do |index|
    node = `nodes[index]`

    # Return if an element
    if `node.nodeType === 1`
      return node
    end
  end

  return nil
end

#go(url) ⇒ Object



185
186
187
188
189
# File 'lib/volt/controllers/model_controller.rb', line 185

def go(url)
  Volt.logger.warn('Deprecation warning: `go` has been renamed to `redirect_to` for consistency with other frameworks.')

  redirect_to(url)
end

#loaded?Boolean

loaded? is a quick way to see if the model for the controller is loaded yet. If the model is there, it asks the model if its loaded. If the model was set to a promise, it waits for the promise to resolve.

Returns:



207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/volt/controllers/model_controller.rb', line 207

def loaded?
  if model.respond_to?(:loaded?)
    # There is a model and it is loaded
    return model.loaded?
  elsif last_promise || model.is_a?(Promise)
    # The model is a promise or is resolving
    return false
  else
    # Otherwise, its loaded
    return true
  end
end

#modelObject



138
139
140
141
142
143
144
145
# File 'lib/volt/controllers/model_controller.rb', line 138

def model
  model = self.current_model

  # If the model is a proc, call it now
  model = model.call if model && model.is_a?(Proc)

  model
end

#model=(val) ⇒ Object

Sets the current model on this controller



105
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
# File 'lib/volt/controllers/model_controller.rb', line 105

def model=(val)
  if val.is_a?(Promise)
    # Resolve the promise before setting
    self.last_promise = val

    val.then do |result|
      # Only assign if nothing else has been assigned since we started the resolve
      self.model = result if last_promise == val
    end.fail do |err|
      Volt.logger.error("Unable to resolve promise assigned to model on #{inspect}")
    end

    return
  end

  # Clear
  self.last_promise = nil

  # Start with a nil reactive value.
  self.current_model ||= Model.new

  if Symbol === val || String === val
    collections = [:page, :store, :params, :controller]
    if collections.include?(val.to_sym)
      self.current_model = send(val)
    else
      fail "#{val} is not the name of a valid model, choose from: #{collections.join(', ')}"
    end
  else
    self.current_model = val
  end
end

#raw(str) ⇒ Object

Raw marks a string as html safe, so bindings can be rendered as html. With great power comes great responsibility.



231
232
233
234
235
236
237
238
239
240
241
# File 'lib/volt/controllers/model_controller.rb', line 231

def raw(str)
  # Promises need to have .to_s called using .then, since .to_s is a promise
  # method, so it won't be passed down to the value.
  if str.is_a?(Promise)
    str = str.then(&:to_s)
  else
    str = str.to_s unless str.is_a?(String)
  end

  str.html_safe
end

#redirect_to(url) ⇒ Object

Change the url



192
193
194
195
196
197
198
# File 'lib/volt/controllers/model_controller.rb', line 192

def redirect_to(url)
  # We might be in the rendering loop, so wait until the next tick before
  # we change the url
  Timers.next_tick do
    self.url.parse(url)
  end
end

#require_login(message = 'You must login to access this area.') ⇒ Object



220
221
222
223
224
225
226
227
# File 'lib/volt/controllers/model_controller.rb', line 220

def (message = 'You must login to access this area.')
  unless Volt.current_user_id
    flash._notices << message
    redirect_to '/login'

    stop_chain
  end
end

#respond_to?(method_name) ⇒ Boolean

Check if this controller responds_to method, or the model

Returns:



244
245
246
247
248
249
250
# File 'lib/volt/controllers/model_controller.rb', line 244

def respond_to?(method_name)
  super || begin
    model = self.model

    model.respond_to?(method_name) if model
  end
end

#trigger(event, *args) ⇒ Object



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/volt/controllers/model_controller.rb', line 82

def trigger(event, *args)
  # Trigger on the current controller if an e- was setup on the component.
  component_event = attrs.send(:"e_#{event}")

  if component_event
    # Add a nil arg for the event, trim to arity
    args2 = (args + [nil])[0...component_event.arity]
    component_event.call(*args2)
  end

  args.unshift(self)
  # Trigger via jquery, so it bubbles up through the DOM
  `$(#{first_element}).trigger(#{event}, #{args});`

  # return nil, so we return a ruby object
  nil
end

#uObject

the u method provides an easy helper to render an unbonud binding. This means that the binding will not reactively update. If no bindings are bound on any model’s from a query, the query will not be reactively listened to.



65
66
67
68
# File 'lib/volt/controllers/model_controller.rb', line 65

def u
  raise "the 'u' method requires a block" unless block_given?
  Volt::Computation.run_without_tracking { yield }
end

#yield_htmlObject

yield_html renders the content passed into a tag as a string. You can “‘.watch!“` “`yield_html“` and it will be run again when anything in the template changes.



72
73
74
75
76
77
78
79
80
# File 'lib/volt/controllers/model_controller.rb', line 72

def yield_html
  if (template_path = attrs.content_template_path)
    @yield_renderer ||= StringTemplateRenderer.new(@volt_app, attrs.content_controller, template_path)
    @yield_renderer.html
  else
    # no template, empty string
    ''
  end
end