metaruby 1.0.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+