class AutoForme::Action

  1. lib/autoforme/action.rb
Superclass: Object

Represents an action on a model in response to a web request.

Constants

ALL_SUPPORTED_ACTIONS = %w'new create show edit update delete destroy browse search mtm_edit mtm_update association_links autocomplete'.freeze  

Array of strings for all action types currently supported

NON_IDEMPOTENT_TYPES = NORMALIZED_ACTION_MAP = {:create=>:new, :update=>:edit, :destroy=>:delete, :mtm_update=>:mtm_edit}  

Map of regular type symbols to normalized type symbols

TITLE_MAP = {:new=>'New', :show=>'Show', :edit=>'Edit', :delete=>'Delete', :browse=>'Browse', :search=>'Search', :mtm_edit=>'Many To Many Edit'}  

Map of type symbols to HTML titles

Attributes

model [R]

The AutoForme::Model instance related to the current action

normalized_type [R]

The normalized type symbol related to the current action, so that paired actions such as new and create both use :new.

output_filename [R]

The filename to use for file output

output_type [R]

The type of output, currently nil for normal output, and ‘csv’ for csv output

params_association [R]

An association symbol for the current related association.

request [R]

The AutoForme::Request instance related to the current action

title [R]

An string suitable for use as the HTML title on the displayed page

type [R]

The type symbols related to the current action (e.g. :new, :create).

Public Class methods

new(model, request)

Creates a new action for the model and request. This action is not usable unless supported? is called first and it returns true.

[show source]
   # File lib/autoforme/action.rb
44 def initialize(model, request)
45   @model = model
46   @request = request
47 end

Public Instance methods

base_url_for(page)

A path for the given page tied to the framework, but not the current model. Used for linking to other models in the same framework.

[show source]
    # File lib/autoforme/action.rb
109 def base_url_for(page)
110   "#{request.path}#{model.framework.prefix}/#{page}"
111 end
column_label_for(type, request, model, column)

The label to use for the given column.

[show source]
    # File lib/autoforme/action.rb
172 def column_label_for(type, request, model, column)
173   unless label = model.column_options_for(type, request, column)[:label]
174     label = humanize(column)
175   end
176   label
177 end
column_options_for(type, request, obj, column)

The options to use for the given column, which will be passed to Forme::Form#input.

[show source]
    # File lib/autoforme/action.rb
159 def column_options_for(type, request, obj, column)
160   opts = model.column_options_for(type, request, column)
161   if opts[:class] == 'autoforme_autocomplete'
162     if type == :show
163       opts[:value] = model.column_value(type, request, obj, column)
164     elsif key = obj.send(model.association_key(column))
165       opts[:value] = "#{key} - #{model.column_value(type, request, obj, column)}"
166     end
167   end
168   opts
169 end
csv(meth)

Return a string in CSV format with the results

[show source]
    # File lib/autoforme/action.rb
180 def csv(meth)
181   @output_type = 'csv'
182   @output_filename = "#{model.link.downcase}_#{normalized_type}.csv"
183   columns = model.columns_for(type, request)
184   headers = columns.map{|column| column_label_for(type, request, model, column)}
185   EnumCSV.csv(model.send(meth, normalized_type, request, :all_results=>true), :headers=>headers) do |obj|
186     columns.map{|column| model.column_value(type, request, obj, column)}
187   end
188 end
edit_page(obj)

The page to use when editing the object.

[show source]
    # File lib/autoforme/action.rb
346 def edit_page(obj)
347   page do
348     t = String.new
349     form_attr = form_attributes(:action=>url_for("update/#{model.primary_key_value(obj)}"))
350     t << Forme.form(obj, form_attr, form_opts(form_attr[:action])) do |f|
351       model.columns_for(:edit, request).each do |column|
352         col_opts = column_options_for(:edit, request, obj, column)
353         if html = model.edit_html_for(obj, column, :edit, request)
354           col_opts = col_opts.merge(:html=>html)
355         end
356         f.input(column, col_opts)
357       end
358       f.button(:value=>'Update', :class=>'btn btn-primary')
359     end
360     if model.supported_action?(:delete, request)
361       t << Forme.form(form_attributes(:action=>url_for("delete/#{model.primary_key_value(obj)}")), form_opts) do |f|
362         f.button(:value=>'Delete', :class=>'btn btn-danger')
363       end
364     end
365     t << association_links(obj)
366   end
367 end
form_attributes(attrs)

Merge the model’s form attributes into the given form attributes, yielding the attributes to use for the form.

[show source]
    # File lib/autoforme/action.rb
242 def form_attributes(attrs)
243   attrs.merge(model.form_attributes_for(type, request))
244 end
form_opts(action=nil)

Options to use for the form. If the form uses POST, automatically adds the CSRF token.

[show source]
    # File lib/autoforme/action.rb
226 def form_opts(action=nil)
227   opts = model.form_options_for(type, request).dup
228 
229   opts[:_before_post] = lambda do |form|
230     if csrf_hash = request.csrf_token_hash(action)
231       csrf_hash.each do |name, value|
232         form.tag(:input, :type=>:hidden, :name=>name, :value=>value)
233       end
234     end
235   end
236 
237   opts
238 end
h(s)

Convert input to a string adn HTML escape it.

[show source]
   # File lib/autoforme/action.rb
97 def h(s)
98   Rack::Utils.escape_html(s.to_s)
99 end
handle()

Handle the current action, returning an HTML string containing the page content, or redirecting.

[show source]
    # File lib/autoforme/action.rb
147 def handle
148   model.before_action_hook(type, request)
149   send("handle_#{type}")
150 end
handle_autocomplete()

Handle autocomplete action by returning a string with one line per model object.

[show source]
    # File lib/autoforme/action.rb
545 def handle_autocomplete
546   if (query = request.params['q'].to_s).empty?
547     ''
548   else
549     model.autocomplete(:type=>@subtype, :request=>request, :association=>params_association, :query=>query, :exclude=>request.params['exclude']).join("\n")
550   end
551 end
handle_browse()

Handle browse action by showing a table containing model objects.

[show source]
    # File lib/autoforme/action.rb
442 def handle_browse
443   if request.id == 'csv'
444     csv(:browse)
445   else
446     table_page(*model.browse(type, request))
447   end
448 end
handle_create()

Handle the create action by creating a new model object.

[show source]
    # File lib/autoforme/action.rb
271 def handle_create
272   obj = model.new(nil, request)
273   model.set_fields(obj, :new, request, model_params)
274   model.hook(:before_create, request, obj)
275   if model.save(obj)
276     model.hook(:after_create, request, obj)
277     request.set_flash_notice("Created #{model.class_name}")
278     redirect(:new, obj)
279   else
280     request.set_flash_now_error("Error Creating #{model.class_name}")
281     new_page(obj)
282   end
283 end
handle_delete()

Handle the edit action by showing a list page if there is no model object selected, or a confirmation screen if there is one.

[show source]
    # File lib/autoforme/action.rb
396 def handle_delete
397   if request.id
398     handle_show
399   else
400     list_page(:delete, :form=>{:action=>url_for('delete')})
401   end
402 end
handle_destroy()

Handle the destroy action by destroying the model object.

[show source]
    # File lib/autoforme/action.rb
405 def handle_destroy
406   obj = model.with_pk(normalized_type, request, request.id)
407   model.hook(:before_destroy, request, obj)
408   model.destroy(obj)
409   model.hook(:after_destroy, request, obj)
410   request.set_flash_notice("Deleted #{model.class_name}")
411   redirect(:delete, obj)
412 end
handle_edit()

Handle the edit action by showing a list page if there is no model object selected, or the edit page if there is one.

[show source]
    # File lib/autoforme/action.rb
370 def handle_edit
371   if request.id
372     obj = model.with_pk(normalized_type, request, request.id)
373     model.hook(:before_edit, request, obj)
374     edit_page(obj)
375   else
376     list_page(:edit)
377   end
378 end
handle_mtm_edit()

Handle the mtm_edit action by showing a list page if there is no model object selected, a list of associations for that model if there is a model object but no association selected, or a mtm_edit form if there is a model object and association selected.

[show source]
    # File lib/autoforme/action.rb
475 def handle_mtm_edit
476   if request.id
477     obj = model.with_pk(:edit, request, request.id)
478     unless assoc = params_association
479       options = model.mtm_association_select_options(request)
480       if options.length == 1
481         assoc = options.first
482       end
483     end
484     if assoc
485       page do
486         t = String.new
487         t << "<h2>Edit #{humanize(assoc)} for #{h model.object_display_name(type, request, obj)}</h2>"
488         form_attr = form_attributes(:action=>url_for("mtm_update/#{model.primary_key_value(obj)}?association=#{assoc}"))
489         t << Forme.form(obj, form_attr, form_opts(form_attr[:action])) do |f|
490           opts = model.column_options_for(:mtm_edit, request, assoc)
491           add_opts = opts[:add] ? opts.merge(opts.delete(:add)) : opts
492           remove_opts = opts[:remove] ? opts.merge(opts.delete(:remove)) : opts
493           add_opts = {:name=>'add[]', :id=>'add', :label=>'Associate With'}.merge!(add_opts)
494           if model.association_autocomplete?(assoc, request)
495             f.input(assoc, {:type=>'text', :class=>'autoforme_autocomplete', :attr=>{'data-type'=>'association', 'data-column'=>assoc, 'data-exclude'=>model.primary_key_value(obj)}, :value=>''}.merge!(add_opts))
496           else
497             f.input(assoc, {:dataset=>model.unassociated_mtm_objects(request, assoc, obj), :size=>10}.merge!(add_opts))
498           end
499           f.input(assoc, {:name=>'remove[]', :id=>'remove', :label=>'Disassociate From', :dataset=>model.associated_mtm_objects(request, assoc, obj), :value=>[], :size=>10}.merge!(remove_opts))
500           f.button(:value=>'Update', :class=>'btn btn-primary')
501         end
502       end
503     else
504       page do
505         Forme.form(form_attributes(:action=>"mtm_edit/#{model.primary_key_value(obj)}"), form_opts) do |f|
506           f.input(:select, :options=>options, :name=>'association', :id=>'association', :label=>'Association', :add_blank=>true)
507           f.button(:value=>'Edit', :class=>'btn btn-primary')
508         end
509       end
510     end
511   else
512     list_page(:edit, :form=>{})
513   end
514 end
handle_mtm_update()

Handle mtm_update action by updating the related many to many association. For ajax requests, return an HTML fragment to update the page, otherwise redirect to the appropriate form.

[show source]
    # File lib/autoforme/action.rb
518 def handle_mtm_update
519   obj = model.with_pk(:edit, request, request.id)
520   assoc = params_association
521   assoc_obj = model.mtm_update(request, assoc, obj, request.params['add'], request.params['remove'])
522   request.set_flash_notice("Updated #{assoc} association for #{model.class_name}") unless request.xhr?
523   if request.xhr?
524     if request.params['add']
525       @type = :edit
526       mtm_edit_remove(assoc, model.associated_model_class(assoc), obj, assoc_obj)
527     else
528       "<option value=\"#{model.primary_key_value(assoc_obj)}\">#{model.associated_object_display_name(assoc, request, assoc_obj)}</option>"
529     end
530   elsif request.params['redir'] == 'edit'
531     redirect(:edit, obj)
532   else
533     redirect(:mtm_edit, obj)
534   end
535 end
handle_new()

Handle the new action by always showing the new form.

[show source]
    # File lib/autoforme/action.rb
264 def handle_new
265   obj = model.new(request.params[model.link], request)
266   model.hook(:before_new, request, obj)
267   new_page(obj)
268 end
handle_show()

Handle the show action by showing a list page if there is no model object selected, or the show page if there is one.

[show source]
    # File lib/autoforme/action.rb
337 def handle_show
338   if request.id
339     show_page(model.with_pk(normalized_type, request, request.id))
340   else
341     list_page(:show)
342   end
343 end
handle_update()

Handle the update action by updating the current model object.

[show source]
    # File lib/autoforme/action.rb
381 def handle_update
382   obj = model.with_pk(normalized_type, request, request.id)
383   model.set_fields(obj, :edit, request, model_params)
384   model.hook(:before_update, request, obj)
385   if model.save(obj)
386     model.hook(:after_update, request, obj)
387     request.set_flash_notice("Updated #{model.class_name}")
388     redirect(:edit, obj)
389   else
390     request.set_flash_now_error("Error Updating #{model.class_name}")
391     edit_page(obj)
392   end
393 end
humanize(string)

Convert the given object into a suitable human readable string.

[show source]
    # File lib/autoforme/action.rb
153 def humanize(string)
154   string = string.to_s
155   string.respond_to?(:humanize) ? string.humanize : string.gsub(/_/, " ").capitalize
156 end
inline_mtm_edit_forms(obj)

HTML fragment used for the inline mtm_edit forms on the edit page.

[show source]
    # File lib/autoforme/action.rb
645 def inline_mtm_edit_forms(obj)
646   assocs = model.inline_mtm_assocs(request)
647   return if assocs.empty?
648 
649   t = String.new
650   t << "<div class='inline_mtm_add_associations'>"
651   assocs.each do |assoc|
652     form_attr = form_attributes(:action=>url_for("mtm_update/#{model.primary_key_value(obj)}?association=#{assoc}&redir=edit"), :class => 'mtm_add_associations', 'data-remove' => "##{assoc}_remove_list")
653     t << Forme.form(obj, form_attr, form_opts(form_attr[:action])) do |f|
654       opts = model.column_options_for(:mtm_edit, request, assoc)
655       add_opts = opts[:add] ? opts.merge(opts.delete(:add)) : opts.dup
656       add_opts = {:name=>'add[]', :id=>"add_#{assoc}"}.merge!(add_opts)
657       if model.association_autocomplete?(assoc, request)
658         f.input(assoc, {:type=>'text', :class=>'autoforme_autocomplete', :attr=>{'data-type'=>'association', 'data-column'=>assoc, 'data-exclude'=>model.primary_key_value(obj)}, :value=>''}.merge!(add_opts))
659       else
660         f.input(assoc, {:dataset=>model.unassociated_mtm_objects(request, assoc, obj), :multiple=>false, :add_blank=>true}.merge!(add_opts))
661       end
662       f.button(:value=>'Add', :class=>'btn btn-xs btn-primary')
663     end
664   end
665   t << "</div>"
666   t << "<div class='inline_mtm_remove_associations'><ul>"
667   assocs.each do |assoc|
668     mc = model.associated_model_class(assoc)
669     t << "<li>"
670     t << association_class_link(mc, assoc)
671     t << "<ul id='#{assoc}_remove_list'>"
672     obj.send(assoc).each do |assoc_obj|
673       t << mtm_edit_remove(assoc, mc, obj, assoc_obj)
674     end
675     t << "</ul></li>"
676   end
677   t << "</ul></div>"
678 end
list_page(type, opts={})

Shared page used by show, edit, and delete actions that shows a list of available model objects (or an autocompleting box), and allows the user to choose one to act on.

[show source]
    # File lib/autoforme/action.rb
287 def list_page(type, opts={})
288   page do
289     form_attr = form_attributes(opts[:form] || {:action=>url_for(type)})
290     Forme.form(form_attr, form_opts) do |f|
291       input_opts = {:name=>'id', :id=>'id', :label=>model.class_name}
292       if model.autocomplete_options_for(type, request)
293         input_type = :text
294         input_opts.merge!(:class=>'autoforme_autocomplete', :attr=>{'data-type'=>type})
295       else
296         input_type = :select
297         input_opts.merge!(:options=>model.select_options(type, request), :add_blank=>true)
298       end
299       f.input(input_type, input_opts)
300       f.button(:value=>type.to_s.capitalize, :class=>"btn btn-#{type == :delete ? 'danger' : 'primary'}")
301     end
302   end
303 end
model_params()

Get request parameters for the model. Used when retrieving form values that use namespacing.

[show source]
    # File lib/autoforme/action.rb
103 def model_params
104   request.params[model.params_name]
105 end
mtm_edit_remove(assoc, mc, obj, assoc_obj)

Line item containing form to remove the currently associated object.

[show source]
    # File lib/autoforme/action.rb
681 def mtm_edit_remove(assoc, mc, obj, assoc_obj)
682   t = String.new
683   t << "<li>"
684   t << association_link(mc, assoc_obj)
685   form_attr = form_attributes(:action=>url_for("mtm_update/#{model.primary_key_value(obj)}?association=#{assoc}&remove%5b%5d=#{model.primary_key_value(assoc_obj)}&redir=edit"), :method=>'post', :class => 'mtm_remove_associations', 'data-add'=>"#add_#{assoc}")
686   t << Forme.form(form_attr, form_opts(form_attr[:action])) do |f|
687     f.button(:value=>'Remove', :class=>'btn btn-xs btn-danger')
688   end
689   t << "</li>"
690 end
new_page(obj, opts={})

HTML content used for the new action

[show source]
    # File lib/autoforme/action.rb
247 def new_page(obj, opts={})
248   page do
249     form_attr = form_attributes(:action=>url_for("create"))
250     Forme.form(obj, form_attr, form_opts(form_attr[:action])) do |f|
251       model.columns_for(:new, request).each do |column|
252         col_opts = column_options_for(:new, request, obj, column)
253         if html = model.edit_html_for(obj, column, :new, request)
254           col_opts = col_opts.merge(:html=>html)
255         end
256         f.input(column, col_opts)
257       end
258       f.button(:value=>'Create', :class=>'btn btn-primary')
259     end
260   end
261 end
page()

Yields and wraps the returned data in a header and footer for the page.

[show source]
    # File lib/autoforme/action.rb
215 def page
216   html = String.new
217   html << (model.page_header_for(type, request) || tabs)
218   html << "<div id='autoforme_content' data-url='#{url_for('')}'>"
219   html << yield.to_s
220   html << "</div>"
221   html << model.page_footer_for(type, request).to_s
222   html
223 end
redirect(type, obj)

Redirect to a page based on the type of action and the given object.

[show source]
    # File lib/autoforme/action.rb
124 def redirect(type, obj)
125   if redir = model.redirect_for
126     path = redir.call(obj, type, request)
127   end
128 
129   unless path
130     path = case type
131     when :new, :delete
132       type.to_s
133     when :edit
134       "edit/#{model.primary_key_value(obj)}"
135     else # when :mtm_edit
136       "mtm_edit/#{model.primary_key_value(obj)}?association=#{params_association}"
137     end
138     path = url_for(path)
139   end
140 
141   request.redirect(path)
142   nil
143 end
show_page(obj)

The page to use when displaying an object, always used as a confirmation screen when deleting an object.

[show source]
    # File lib/autoforme/action.rb
306 def show_page(obj)
307   page do
308     t = String.new
309     f = Forme::Form.new(obj, :formatter=>:readonly, :wrapper=>:trtd, :labeler=>:explicit)
310     t << "<table class=\"#{model.table_class_for(:show, request)}\">"
311     model.columns_for(type, request).each do |column|
312       col_opts = column_options_for(type, request, obj, column)
313       if html = model.show_html_for(obj, column, type, request)
314         col_opts = col_opts.merge(:html=>html)
315       end
316       t << f.input(column, col_opts)
317     end
318     t << '</table>'
319     if type == :show && model.supported_action?(:edit, request)
320       t << Forme.form(form_attributes(:action=>url_for("edit/#{model.primary_key_value(obj)}")), form_opts) do |f1|
321         f1.button(:value=>'Edit', :class=>'btn btn-primary')
322       end
323     end
324     if type == :delete
325       form_attr = form_attributes(:action=>url_for("destroy/#{model.primary_key_value(obj)}"), :method=>:post)
326       t << Forme.form(form_attr, form_opts(form_attr[:action])) do |f1|
327         f1.button(:value=>'Delete', :class=>'btn btn-danger')
328       end
329     else
330       t << association_links(obj)
331     end
332     t
333   end
334 end
subtype()

The subtype of request, used for association_links and autocomplete actions.

[show source]
    # File lib/autoforme/action.rb
119 def subtype
120   ((t = request.params['type']) && ALL_SUPPORTED_ACTIONS.include?(t) && t.to_sym) || :edit
121 end
supported?()

Return true if the action is supported, and false otherwise. An action may not be supported if the type is not one of the supported types, or if an non-idemponent request is issued with get instead of post, or potentionally other reasons.

As a side-effect, this sets up additional state related to the request.

[show source]
   # File lib/autoforme/action.rb
55 def supported?
56   return false unless ALL_SUPPORTED_ACTIONS.include?(request.action_type)
57   type = @type = request.action_type.to_sym
58   @normalized_type = NORMALIZED_ACTION_MAP.fetch(type, type)
59   return false if NON_IDEMPOTENT_TYPES[type] && !request.post?
60 
61   case type
62   when :mtm_edit
63     return false unless model.supported_action?(type, request)
64     if request.id && (assoc = request.params['association'])
65       return false unless model.supported_mtm_edit?(assoc, request)
66       @params_association = assoc.to_sym
67     end
68 
69     @title = "#{model.class_name} - #{TITLE_MAP[type]}"
70   when :mtm_update
71     return false unless request.id && (assoc = request.params['association']) && model.supported_mtm_update?(assoc, request)
72     @params_association = assoc.to_sym
73   when :association_links
74     @subtype = subtype
75     return false unless model.supported_action?(@subtype, request)
76   when :autocomplete
77     if assoc = request.id
78       return false unless model.association?(assoc)
79       @params_association = assoc.to_sym
80       @subtype = :association
81       return false unless associated_class = model.associated_model_class(@params_association)
82       return false unless associated_class.autocomplete_options_for(@subtype, request)
83     else
84       @subtype = subtype
85       return false unless model.autocomplete_options_for(@subtype, request)
86     end
87   else
88     return false unless model.supported_action?(normalized_type, request)
89 
90     @title = "#{model.class_name} - #{TITLE_MAP[@normalized_type]}"
91   end
92 
93   true
94 end
tab_name(type)

The name to give the tab for the given type.

[show source]
    # File lib/autoforme/action.rb
203 def tab_name(type)
204   case type
205   when :browse
206     model.class_name
207   when :mtm_edit
208     'MTM'
209   else
210     type.to_s.capitalize
211   end
212 end
table_page(next_page, objs)

Show page used for browse/search pages.

[show source]
    # File lib/autoforme/action.rb
435 def table_page(next_page, objs)
436   page do
437     Table.new(self, objs).to_s << table_pager(normalized_type, next_page)
438   end
439 end
table_pager(type, next_page)

HTML fragment for the table pager, showing links to next page or previous page for browse/search forms.

[show source]
    # File lib/autoforme/action.rb
415 def table_pager(type, next_page)
416   html = String.new
417   html << '<ul class="pager">'
418   page = request.id.to_i
419   if page > 1
420     html << "<li><a href=\"#{url_for("#{type}/#{page-1}?#{h request.query_string}")}\">Previous</a></li>"
421   else
422     html << '<li class="disabled"><a href="#">Previous</a></li>'
423   end
424   if next_page
425     page = 1 if page < 1
426     html << "<li><a href=\"#{url_for("#{type}/#{page+1}?#{h request.query_string}")}\">Next</a></li>"
427   else
428     html << '<li class="disabled"><a href="#">Next</a></li>'
429   end
430   html << "</ul>"
431   html << "<p><a href=\"#{url_for("#{type}/csv?#{h request.query_string}")}\">CSV Format</a></p>"
432 end
tabs()

HTML fragment for the default page header, which uses tabs for each supported action.

[show source]
    # File lib/autoforme/action.rb
191 def tabs
192   content = String.new
193   content << '<ul class="nav nav-tabs">'
194   Model::DEFAULT_SUPPORTED_ACTIONS.each do |action_type|
195     if model.supported_action?(action_type, request)
196       content << "<li class=\"nav-item #{'active' if type == action_type}\"><a class=\"nav-link\" href=\"#{url_for(action_type)}\">#{tab_name(action_type)}</a></li>"
197     end
198   end
199   content << '</ul>'
200 end
url_for(page)

A path for the given page for the same model.

[show source]
    # File lib/autoforme/action.rb
114 def url_for(page)
115   base_url_for("#{model.link}/#{page}")
116 end