metaruby 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/.gemtest +0 -0
  3. data/History.txt +1 -0
  4. data/Manifest.txt +40 -0
  5. data/README.md +318 -0
  6. data/Rakefile +39 -0
  7. data/lib/metaruby/class.rb +120 -0
  8. data/lib/metaruby/dsls/doc.rb +78 -0
  9. data/lib/metaruby/dsls/find_through_method_missing.rb +76 -0
  10. data/lib/metaruby/dsls.rb +2 -0
  11. data/lib/metaruby/gui/exception_view.rb +124 -0
  12. data/lib/metaruby/gui/html/button.rb +65 -0
  13. data/lib/metaruby/gui/html/collection.rb +103 -0
  14. data/lib/metaruby/gui/html/exception_view.css +8 -0
  15. data/lib/metaruby/gui/html/jquery.min.js +154 -0
  16. data/lib/metaruby/gui/html/jquery.selectfilter.js +65 -0
  17. data/lib/metaruby/gui/html/jquery.tagcloud.min.js +8 -0
  18. data/lib/metaruby/gui/html/jquery.tinysort.min.js +8 -0
  19. data/lib/metaruby/gui/html/list.rhtml +24 -0
  20. data/lib/metaruby/gui/html/page.css +55 -0
  21. data/lib/metaruby/gui/html/page.rb +283 -0
  22. data/lib/metaruby/gui/html/page.rhtml +13 -0
  23. data/lib/metaruby/gui/html/page_body.rhtml +17 -0
  24. data/lib/metaruby/gui/html/rock-website.css +694 -0
  25. data/lib/metaruby/gui/html.rb +16 -0
  26. data/lib/metaruby/gui/model_browser.rb +262 -0
  27. data/lib/metaruby/gui/model_selector.rb +266 -0
  28. data/lib/metaruby/gui/rendering_manager.rb +112 -0
  29. data/lib/metaruby/gui/ruby_constants_item_model.rb +253 -0
  30. data/lib/metaruby/gui.rb +9 -0
  31. data/lib/metaruby/inherited_attribute.rb +482 -0
  32. data/lib/metaruby/module.rb +158 -0
  33. data/lib/metaruby/registration.rb +157 -0
  34. data/lib/metaruby/test.rb +79 -0
  35. data/lib/metaruby.rb +17 -0
  36. data/manifest.xml +12 -0
  37. data/test/suite.rb +15 -0
  38. data/test/test_attributes.rb +323 -0
  39. data/test/test_class.rb +68 -0
  40. data/test/test_dsls.rb +49 -0
  41. data/test/test_module.rb +105 -0
  42. data/test/test_registration.rb +182 -0
  43. metadata +160 -0
@@ -0,0 +1,16 @@
1
+ require 'metaruby/gui/html/button'
2
+ require 'metaruby/gui/html/page'
3
+ require 'metaruby/gui/html/collection'
4
+
5
+ module MetaRuby
6
+ module GUI
7
+ module HTML
8
+ def self.escape_html(string)
9
+ string.
10
+ gsub('<', '&lt;').
11
+ gsub('>', '&gt;')
12
+ end
13
+ end
14
+ end
15
+ end
16
+
@@ -0,0 +1,262 @@
1
+ module MetaRuby
2
+ module GUI
3
+ # Widget that allows to browse the currently available models and
4
+ # display information about them
5
+ #
6
+ # It contains a model selection widget, which lists all available models
7
+ # and allows to select them, and a visualization pane in which the
8
+ # corresponding model visualizations are rendered as HTML
9
+ #
10
+ # The object display itself is delegated to rendering objects. These
11
+ # objects must respond to:
12
+ #
13
+ # #enable: enable this renderer. This is called so that the rendering
14
+ # object listens to relevant Qt signals if it has e.g. the ability to
15
+ # interact with the user through HTML buttons
16
+ # #disable: disables this renderer. This is called so that the rendering
17
+ # object can stop listening to relevant Qt signals if it has e.g. the ability to
18
+ # interact with the user through HTML buttons
19
+ # #clear: clear existing data
20
+ # #render(model): render the given model
21
+ #
22
+ class ModelBrowser < Qt::Widget
23
+ # @return [ModelSelector] the widget that lists available models and
24
+ # allows to select them
25
+ attr_reader :model_selector
26
+ # @return [Page] the page object that handles compositing the
27
+ # results of different rendering objects, as well as the ability
28
+ # to e.g. handle buttons
29
+ attr_reader :page
30
+ # @return [Qt::WebView] the HTML view widget
31
+ attr_reader :display
32
+ # @return [ExceptionView] view that allows to display errors to the
33
+ # user
34
+ attr_reader :exception_view
35
+ # @return [Qt::PushButton] button that causes model reloading
36
+ attr_reader :btn_reload_models
37
+ # @return [RenderingManager] the object that manages all the
38
+ # rendering objects available
39
+ attr_reader :manager
40
+ # @return [Array<Exception>] set of exceptions raised during the
41
+ # last rendering step
42
+ attr_reader :registered_exceptions
43
+ # @return [Array<Model,[String]>] the browsing history, as either
44
+ # direct modules or module name path (suitable to be given to
45
+ # #select_by_path)
46
+ attr_reader :history
47
+ # @return [Integer] the index of the current link in the history
48
+ attr_reader :history_index
49
+
50
+ # A Page object with a #link_to method that is suitable for the
51
+ # model browser
52
+ class Page < HTML::Page
53
+ def uri_for(object)
54
+ if (obj_name = object.name) && (obj_name =~ /^[\w:]+$/)
55
+ path = obj_name.split("::")
56
+ "/" + path.join("/")
57
+ else super
58
+ end
59
+ end
60
+ end
61
+
62
+
63
+ def initialize(main = nil)
64
+ super
65
+
66
+ @manager = RenderingManager.new
67
+
68
+ main_layout = Qt::VBoxLayout.new(self)
69
+
70
+ menu_layout = Qt::HBoxLayout.new
71
+ main_layout.add_layout(menu_layout)
72
+ central_layout = Qt::HBoxLayout.new
73
+ main_layout.add_layout(central_layout, 3)
74
+ splitter = Qt::Splitter.new(self)
75
+ central_layout.add_widget(splitter)
76
+ @exception_view = ExceptionView.new
77
+ main_layout.add_widget(exception_view, 1)
78
+
79
+ @available_renderers = Hash.new
80
+ @registered_exceptions = Array.new
81
+
82
+ @btn_reload_models = Qt::PushButton.new("Reload", self)
83
+ menu_layout.add_widget(btn_reload_models)
84
+ menu_layout.add_stretch(1)
85
+ update_exceptions
86
+
87
+ @history = Array.new
88
+ @history_index = -1
89
+
90
+ add_central_widgets(splitter)
91
+ setTabOrder(model_selector, display)
92
+ setTabOrder(display, btn_reload_models)
93
+ end
94
+
95
+ # Update the model selector after {register_type} got called
96
+ def update_model_selector
97
+ model_selector.update
98
+ end
99
+
100
+ # Registers a certain kind of model as well as the information
101
+ # needed to display it
102
+ #
103
+ # It registers the given type on the model browser so that it gets
104
+ # displayed there.
105
+ #
106
+ # You must call {update_model_selector} after this call for the
107
+ # modification to have any effect (i.e. for the newly registered
108
+ # models to appear on the selector)
109
+ #
110
+ # @param [Model] type the base model class for the models that are
111
+ # considered here
112
+ # @param [Class] rendering_class a class from which a relevant
113
+ # rendering object can be created. The generated instances must
114
+ # follow the rules described in the documentation of
115
+ # {ModelBrowser}
116
+ # @param [String] name the name that should be used for this
117
+ # category
118
+ # @param [Integer] priority the priority of this category. Some
119
+ # models might be submodels of various types at the same time (as
120
+ # e.g. when both a model and its supermodel are registered here).
121
+ # The one with the highest priority will be used.
122
+ def register_type(type, rendering_class, name, priority = 0)
123
+ model_selector.register_type(type, name, priority)
124
+ manager.register_type(type, rendering_class)
125
+ end
126
+
127
+ # Sets up the widgets that form the central part of the browser
128
+ def add_central_widgets(splitter)
129
+ @model_selector = ModelSelector.new
130
+ splitter.add_widget(model_selector)
131
+
132
+ # Create a central stacked layout
133
+ display = @display = Qt::WebView.new
134
+ browser = self
135
+ display.singleton_class.class_eval do
136
+ define_method :contextMenuEvent do |event|
137
+ menu = Qt::Menu.new(self)
138
+ act = page.action(Qt::WebPage::Back)
139
+ act.enabled = true
140
+ menu.add_action act
141
+ connect(act, SIGNAL(:triggered), browser, SLOT(:back))
142
+ act = page.action(Qt::WebPage::Forward)
143
+ act.enabled = true
144
+ connect(act, SIGNAL(:triggered), browser, SLOT(:forward))
145
+ menu.add_action act
146
+ menu.popup(event.globalPos)
147
+ event.accept
148
+ end
149
+ end
150
+ splitter.add_widget(display)
151
+ splitter.set_stretch_factor(1, 2)
152
+ self.page = Page.new(display.page)
153
+
154
+ model_selector.connect(SIGNAL('model_selected(QVariant)')) do |mod|
155
+ mod = mod.to_ruby
156
+ push_to_history(mod)
157
+ render_model(mod)
158
+ end
159
+ end
160
+
161
+ # Sets the page object that should be used for rendering
162
+ #
163
+ # @param [Page] page the new page object
164
+ def page=(page)
165
+ manager.page = page
166
+ page.connect(SIGNAL('linkClicked(const QUrl&)')) do |url|
167
+ if url.scheme == "link"
168
+ path = url.path
169
+ path = path.split('/')[1..-1]
170
+ select_by_path(*path)
171
+ end
172
+ end
173
+ connect(page, SIGNAL('updated()'), self, SLOT('update_exceptions()'))
174
+ connect(manager, SIGNAL('updated()'), self, SLOT('update_exceptions()'))
175
+ @page = page
176
+ end
177
+
178
+ # Call to render the given model
179
+ #
180
+ # @param [Model] mod the model that should be rendered
181
+ # @raises [ArgumentError] if there is no view available for the
182
+ # given model
183
+ def render_model(mod, options = Hash.new)
184
+ page.clear
185
+ @registered_exceptions.clear
186
+ reference_model, _ = manager.find_renderer(mod)
187
+ if mod
188
+ page.title = "#{mod.name} (#{reference_model.name})"
189
+ begin
190
+ manager.render(mod, options)
191
+ rescue ::Exception => e
192
+ @registered_exceptions << e
193
+ end
194
+ else
195
+ @registered_exceptions << ArgumentError.new("no view available for #{mod} (#{mod.class})")
196
+ end
197
+ update_exceptions
198
+ end
199
+
200
+ # Updates {#exception_view} from the set of registered exceptions
201
+ def update_exceptions
202
+ exception_view.exceptions = registered_exceptions +
203
+ manager.registered_exceptions
204
+ end
205
+ slots 'update_exceptions()'
206
+
207
+ # (see ModelSelector#select_by_module)
208
+ def select_by_path(*path)
209
+ if model_selector.select_by_path(*path)
210
+ push_to_history(path)
211
+ end
212
+ end
213
+
214
+ # (see ModelSelector#select_by_module)
215
+ def select_by_module(model)
216
+ if model_selector.select_by_module(model)
217
+ push_to_history(model)
218
+ end
219
+ end
220
+
221
+ # (see ModelSelector#current_selection)
222
+ def current_selection
223
+ model_selector.current_selection
224
+ end
225
+
226
+ # Pushes one element in the history
227
+ #
228
+ # If the history index is not at the end, the remainder is discarded
229
+ def push_to_history(object)
230
+ return if object == history[history_index]
231
+
232
+ @history = history[0, history_index + 1]
233
+ history << object
234
+ @history_index = history.size - 1
235
+ end
236
+
237
+ # Go forward in the browsing history
238
+ def forward
239
+ return if history_index == history.size - 1
240
+ @history_index += 1
241
+ select_by_history_element(history[history_index])
242
+ end
243
+
244
+ # Go back in the browsing history
245
+ def back
246
+ return if history_index <= 0
247
+ @history_index -= 1
248
+ select_by_history_element(history[history_index])
249
+ end
250
+
251
+ slots :back, :forward
252
+
253
+ # Selects a given model based on a value in the history
254
+ def select_by_history_element(h)
255
+ if h.respond_to?(:to_ary)
256
+ select_by_path(*h)
257
+ else select_by_module(h)
258
+ end
259
+ end
260
+ end
261
+ end
262
+ end
@@ -0,0 +1,266 @@
1
+ module MetaRuby
2
+ module GUI
3
+ # A Qt widget that allows to browse the models registered in the Ruby
4
+ # constanat hierarchy
5
+ class ModelSelector < Qt::Widget
6
+ attr_reader :btn_type_filter_menu
7
+ attr_reader :type_filters
8
+ attr_reader :model_list
9
+ attr_reader :model_filter
10
+ # @return [Qt::LineEdit] the line edit widget that allows to modify
11
+ # the tree view filter
12
+ attr_reader :filter_box
13
+ # @return [Qt::Completer] auto-completion for {filter_box}
14
+ attr_reader :filter_completer
15
+ attr_reader :type_info
16
+ attr_reader :browser_model
17
+
18
+ def initialize(parent = nil)
19
+ super
20
+
21
+ @type_info = Hash.new
22
+ @type_filters = Hash.new
23
+
24
+ layout = Qt::VBoxLayout.new(self)
25
+ filter_button = Qt::PushButton.new('Filters', self)
26
+ layout.add_widget(filter_button)
27
+ @btn_type_filter_menu = Qt::Menu.new
28
+ filter_button.menu = btn_type_filter_menu
29
+
30
+ setup_tree_view(layout)
31
+ setTabOrder(filter_box, filter_button)
32
+ setTabOrder(filter_button, model_list)
33
+ end
34
+
35
+ def register_type(model_base, name, priority = 0)
36
+ type_info[model_base] = RubyConstantsItemModel::TypeInfo.new(name, priority)
37
+ action = Qt::Action.new(name, self)
38
+ action.checkable = true
39
+ action.checked = true
40
+ type_filters[model_base] = action
41
+ btn_type_filter_menu.add_action(action)
42
+ connect(action, SIGNAL('triggered()')) do
43
+ update_model_filter
44
+ end
45
+ end
46
+
47
+ def update
48
+ update_model_filter
49
+ reload
50
+ end
51
+
52
+ def update_model_filter
53
+ type_rx = type_filters.map do |model_base, act|
54
+ if act.checked?
55
+ type_info[model_base].name
56
+ end
57
+ end
58
+ type_rx = type_rx.compact.join("|")
59
+
60
+ model_filter.filter_role = Qt::UserRole # this contains the keywords (ancestry and/or name)
61
+ # This workaround a problem that I did not have time to
62
+ # investigate. Adding new characters to the filter updates the
63
+ # browser just fine, while removing some characters does not
64
+ #
65
+ # This successfully resets the filter
66
+ model_filter.filter_reg_exp = Qt::RegExp.new("")
67
+ # The pattern has to match every element in the hierarchy. We
68
+ # achieve this by making the suffix part optional
69
+ name_rx = filter_box.text.downcase.gsub(/:+/, "/")
70
+ model_filter.filter_reg_exp = Qt::RegExp.new("(#{type_rx}).*;.*#{name_rx}")
71
+ auto_open
72
+ end
73
+
74
+ def auto_open(threshold = 5)
75
+ current_level = [Qt::ModelIndex.new]
76
+ while !current_level.empty?
77
+ count = current_level.inject(0) do |total, index|
78
+ total + model_filter.rowCount(index)
79
+ end
80
+ close_this_level = (count > threshold)
81
+ current_level.each do |index|
82
+ model_filter.rowCount(index).times.each do |row|
83
+ model_list.setExpanded(model_filter.index(row, 0, index), !close_this_level)
84
+ end
85
+ end
86
+ return if close_this_level
87
+
88
+ last_level, current_level = current_level, []
89
+ last_level.each do |index|
90
+ model_filter.rowCount(index).times.each do |row|
91
+ current_level << model_filter.index(row, 0, index)
92
+ end
93
+ end
94
+ end
95
+ end
96
+
97
+ def model?(obj)
98
+ type_info.any? do |model_base, _|
99
+ obj.kind_of?(model_base) ||
100
+ (obj.kind_of?(Module) && obj <= model_base)
101
+ end
102
+ end
103
+
104
+ class ModelPathCompleter < Qt::Completer
105
+ def splitPath(path)
106
+ path.split('/')
107
+ end
108
+ def pathFromIndex(index)
109
+ index.data(Qt::UserRole).split(";").last
110
+ end
111
+ end
112
+
113
+ def all_leaves(model, limit = nil, item = Qt::ModelIndex.new, result = [])
114
+ if !model.hasChildren(item)
115
+ result << item
116
+ return result
117
+ end
118
+
119
+ row, child_item = 0, model.index(0, 0, item)
120
+ while child_item.valid?
121
+ all_leaves(model, limit, child_item, result)
122
+ if limit && result.size == limit
123
+ return result
124
+ end
125
+ row += 1
126
+ child_item = model.index(row, 0, item)
127
+ end
128
+ return result
129
+ end
130
+
131
+ def select_first_item
132
+ if item = all_leaves(model_filter, 1).first
133
+ model_list.setCurrentIndex(item)
134
+ end
135
+ end
136
+
137
+ def setup_tree_view(layout)
138
+ @model_list = Qt::TreeView.new(self)
139
+ @model_filter = Qt::SortFilterProxyModel.new
140
+ model_filter.filter_case_sensitivity = Qt::CaseInsensitive
141
+ model_filter.dynamic_sort_filter = true
142
+ model_filter.filter_role = Qt::UserRole
143
+ model_list.model = model_filter
144
+
145
+ @filter_box = Qt::LineEdit.new(self)
146
+ filter_box.connect(SIGNAL('textChanged(QString)')) do |text|
147
+ update_model_filter
148
+ end
149
+ filter_box.connect(SIGNAL('returnPressed()')) do |text|
150
+ select_first_item
151
+ end
152
+ @filter_completer = ModelPathCompleter.new(browser_model, self)
153
+ filter_completer.case_sensitivity = Qt::CaseInsensitive
154
+ filter_box.completer = filter_completer
155
+ layout.add_widget(filter_box)
156
+ layout.add_widget(model_list)
157
+
158
+ model_list.selection_model.connect(SIGNAL('currentChanged(const QModelIndex&, const QModelIndex&)')) do |index, _|
159
+ index = model_filter.map_to_source(index)
160
+ mod = browser_model.info_from_index(index)
161
+ if model?(mod.this)
162
+ emit model_selected(Qt::Variant.from_ruby(mod.this, mod.this))
163
+ end
164
+ end
165
+ end
166
+ signals 'model_selected(QVariant)'
167
+
168
+ def reload
169
+ if current = current_selection
170
+ current_module = current.this
171
+ current_path = []
172
+ while current
173
+ current_path.unshift current.name
174
+ current = current.parent
175
+ end
176
+ end
177
+
178
+ @browser_model = RubyConstantsItemModel.new(type_info) do |mod|
179
+ model?(mod)
180
+ end
181
+ browser_model.reload
182
+ model_filter.source_model = browser_model
183
+
184
+ if current_path && !select_by_path(*current_path)
185
+ select_by_module(current_module)
186
+ end
187
+ end
188
+
189
+ # Resets the current filter
190
+ def reset_filter
191
+ # If there is a filter, reset it and try again
192
+ if !filter_box.text.empty?
193
+ filter_box.text = ""
194
+ true
195
+ end
196
+ end
197
+
198
+ # Maps a model index from the source index to the filtered index,
199
+ # e.g. to select something in the view.
200
+ #
201
+ # In addition to the normal map_from_source call, it allows to
202
+ # control whether the filter should be reset if the index given as
203
+ # parameter is currently filtered out
204
+ #
205
+ # @param [Qt::ModelIndex] an index valid in {browser_model}
206
+ # @option options [Boolean] :reset_filter (true) if true, the filter
207
+ # is reset if the requested index is currently filtered out
208
+ # @return [Qt::ModelIndex] an index filtered by {model_filter}
209
+ def map_index_from_source(source_index, options = Hash.new)
210
+ options = Kernel.validate_options options, :reset_filter => true
211
+ index = model_filter.map_from_source(source_index)
212
+ if !index
213
+ return
214
+ elsif !index.valid?
215
+ if !options[:reset_filter]
216
+ return index
217
+ end
218
+ reset_filter
219
+ model_filter.map_from_source(source_index)
220
+ else index
221
+ end
222
+ end
223
+
224
+ # Selects the current model given a path in the constant names
225
+ # This emits the model_selected signal
226
+ #
227
+ # @return [Boolean] true if the path resolved to something known,
228
+ # and false otherwise
229
+ def select_by_path(*path)
230
+ if index = browser_model.find_index_by_path(*path)
231
+ index = map_index_from_source(index)
232
+ model_list.selection_model.set_current_index(index, Qt::ItemSelectionModel::ClearAndSelect)
233
+ true
234
+ end
235
+ end
236
+
237
+ # Selects the given model if it registered in the model list
238
+ # This emits the model_selected signal
239
+ #
240
+ # @return [Boolean] true if the path resolved to something known,
241
+ # and false otherwise
242
+ def select_by_module(model)
243
+ if index = browser_model.find_index_by_model(model)
244
+ index = map_index_from_source(index)
245
+ model_list.selection_model.set_current_index(index, Qt::ItemSelectionModel::ClearAndSelect)
246
+ true
247
+ end
248
+ end
249
+
250
+ # Returns the currently selected item
251
+ # @return [RubyModuleModel::ModuleInfo,nil] nil if there are no
252
+ # selections
253
+ def current_selection
254
+ index = model_list.selection_model.current_index
255
+ if index.valid?
256
+ index = model_filter.map_to_source(index)
257
+ browser_model.info_from_index(index)
258
+ end
259
+ end
260
+
261
+ def object_paths
262
+ browser_model.object_paths
263
+ end
264
+ end
265
+ end
266
+ end
@@ -0,0 +1,112 @@
1
+ module MetaRuby
2
+ module GUI
3
+ class RenderingManager < Qt::Object
4
+ # @return [#push] the page object on which we render
5
+ attr_reader :page
6
+ # @return [{Model=>Object}] set of rendering objects that are
7
+ # declared. The Model is a class or module that represents the
8
+ # set of models that the renderer can handle.
9
+ # It is ordered by order of priority, i.e. the first model that
10
+ # matches will be used.
11
+ # Do not modify directly, use {#register_type} instead
12
+ attr_reader :available_renderers
13
+ # The last used rendering objects
14
+ attr_reader :current_renderer
15
+
16
+ def initialize(page = nil)
17
+ super()
18
+ @page = page
19
+ @available_renderers = Hash.new
20
+ end
21
+
22
+ def registered_exceptions
23
+ if current_renderer.respond_to?(:registered_exceptions)
24
+ current_renderer.registered_exceptions
25
+ else []
26
+ end
27
+ end
28
+
29
+ # Registers a certain kind of model as well as the information
30
+ # needed to display it
31
+ #
32
+ # It registers the given type on the model browser so that it gets
33
+ # displayed there.
34
+ #
35
+ # @param [Class] type the base model class for the models that are
36
+ # considered here
37
+ # @param [Class] rendering_class a class from which a relevant
38
+ # rendering object can be created. The generated instances must
39
+ # follow the rules described in the documentation of
40
+ # {ModelBrowser}
41
+ def register_type(type, rendering_class, render_options = Hash.new)
42
+ render = if rendering_class.kind_of?(Class)
43
+ rendering_class.new(page)
44
+ else
45
+ rendering_class
46
+ end
47
+ available_renderers[type] = [render, render_options]
48
+ connect(render, SIGNAL('updated()'), self, SIGNAL('updated()'))
49
+ end
50
+
51
+ # Changes on which page this rendering manager should act
52
+ def page=(page)
53
+ return if @page == page
54
+ @page = page
55
+ @available_renderers = available_renderers.map_value do |key, (value, render_options)|
56
+ new_render = value.class.new(page)
57
+ disconnect(value, SIGNAL('updated()'))
58
+ connect(new_render, SIGNAL('updated()'), self, SIGNAL('updated()'))
59
+ [new_render, render_options]
60
+ end
61
+ end
62
+
63
+ def find_renderer(mod)
64
+ available_renderers.find do |model, _|
65
+ mod.kind_of?(model) || (mod.kind_of?(Module) && model.kind_of?(Module) && mod <= model)
66
+ end
67
+ end
68
+
69
+ def disable
70
+ if current_renderer
71
+ current_renderer.disable
72
+ end
73
+ end
74
+
75
+ def enable
76
+ if current_renderer
77
+ current_renderer.enable
78
+ end
79
+ end
80
+
81
+ def clear
82
+ if current_renderer
83
+ current_renderer.clear
84
+ end
85
+ end
86
+
87
+
88
+ # Call to render the given model
89
+ #
90
+ # @param [Model] mod the model that should be rendered
91
+ # @raises [ArgumentError] if there is no view available for the
92
+ # given model
93
+ def render(object, push_options = Hash.new)
94
+ _, (renderer, render_options) = find_renderer(object)
95
+ if renderer
96
+ if current_renderer
97
+ current_renderer.clear
98
+ current_renderer.disable
99
+ end
100
+ renderer.enable
101
+ renderer.render(object, render_options.merge(push_options))
102
+ @current_renderer = renderer
103
+ else
104
+ Kernel.raise ArgumentError, "no view available for #{object} (#{object.class})"
105
+ end
106
+ end
107
+
108
+ signals 'updated()'
109
+ end
110
+ end
111
+ end
112
+