class Sequel::Plugins::Forme::SequelInput

Helper class for dealing with Forme/Sequel integration. One instance is created for each call to Forme::Form#input for forms associated with Sequel::Model objects.

Constants

FORME_NAME_METHODS

The name methods that will be tried, in order, to get the text to use for the options in the select input created for associations.

Attributes

field[R]

The field/column name related to the receiver. The type of input created usually depends upon this field.

form[R]

The form related to the receiver.

obj[R]

The Sequel::Model object related to the receiver.

opts[R]

The options hash related to the receiver.

Public Class Methods

new(obj, form, field, opts) click to toggle source

Set the obj, form, field, and opts attributes.

    # File lib/sequel/plugins/forme.rb
150 def initialize(obj, form, field, opts)
151   @obj, @form, @field, @opts = obj, form, field, opts
152 end

Public Instance Methods

input() click to toggle source

Determine which type of input to used based on the field. If the field is a column, use the column’s type to determine an appropriate field type. If the field is an association, use either a regular or multiple select input (or multiple radios or checkboxes if the related :as option is used). If it’s not a column or association, but the object responds to field, create a text input. Otherwise, raise an Error.

    # File lib/sequel/plugins/forme.rb
161 def input
162   opts[:attr] = opts[:attr] ? opts[:attr].dup : {}
163   opts[:wrapper_attr] = opts[:wrapper_attr] ? opts[:wrapper_attr].dup : {}
164   handle_errors(field)
165   handle_validations(field)
166 
167   type = opts[:type]
168   if !type && (sch = obj.db_schema[field])
169     meth = :"input_#{sch[:type]}"
170     opts[:key] = field unless opts.has_key?(:key)
171     opts[:required] = true if !opts.has_key?(:required) && sch[:allow_null] == false && sch[:type] != :boolean
172     handle_label(field)
173 
174     ::Forme.attr_classes(opts[:wrapper_attr], sch[:type])
175     ::Forme.attr_classes(opts[:wrapper_attr], "required") if opts[:required]
176 
177     if respond_to?(meth, true)
178       send(meth, sch)
179     else
180       input_other(sch)
181     end
182   elsif !type && (ref = obj.model.association_reflection(field))
183     ::Forme.attr_classes(opts[:wrapper_attr], ref[:type])
184     meth = :"association_#{ref[:type]}"
185     if respond_to?(meth, true)
186       send(meth, ref)
187     else
188       raise Error, "Association type #{ref[:type]} not currently handled for association #{ref[:name]}"
189     end
190   else
191     rt = obj.respond_to?(field)
192     raise(Error, "Unrecognized field used: #{field}") unless rt || type
193     meth = :"input_#{type}"
194     opts[:value] = nil unless rt || opts.has_key?(:value)
195     opts[:key] = field unless opts.has_key?(:key)
196     handle_label(field)
197     if respond_to?(meth, true)
198       opts.delete(:type)
199       send(meth, opts)
200     else
201       input_other(opts)
202     end
203   end
204 end

Private Instance Methods

_input(*a) click to toggle source

Create an Input instance associated to the receiver’s form with the given arguments.

    # File lib/sequel/plugins/forme.rb
210 def _input(*a)
211   form._input(*a)
212 end
association_many_to_many(ref)
association_many_to_one(ref) click to toggle source

Create a select input made up of options for all entries the object could be associated to, with the one currently associated to being selected. If the :as=>:radio option is used, use multiple radio buttons instead of a select box. For :as=>:radio, you can also provide a :tag_wrapper option used to wrap the individual radio buttons.

    # File lib/sequel/plugins/forme.rb
290 def association_many_to_one(ref)
291   key = ref[:key]
292   handle_errors(key)
293   opts[:key] = key unless opts.has_key?(:key)
294   opts[:value] = obj.send(key) unless opts.has_key?(:value)
295   opts[:options] = association_select_options(ref) unless opts.has_key?(:options)
296   if opts.delete(:as) == :radio
297     handle_label(field)
298     _input(:radioset, opts)
299   else
300     opts[:required] = true if !opts.has_key?(:required) && (sch = obj.model.db_schema[key]) && !sch[:allow_null]
301     opts[:add_blank] = true if !opts.has_key?(:add_blank) && !(opts[:required] && opts[:value])
302     handle_label(field)
303     ::Forme.attr_classes(opts[:wrapper_attr], "required") if opts[:required]
304     _input(:select, opts)
305   end
306 end
association_one_to_many(ref) click to toggle source

Create a multiple select input made up of options for all entries the object could be associated to, with all of the ones currently associated to being selected. If the :as=>:checkbox option is used, use multiple checkboxes instead of a multiple select box. For :as=>:checkbox, you can also provide a :tag_wrapper option used to wrap the individual checkboxes.

    # File lib/sequel/plugins/forme.rb
313 def association_one_to_many(ref)
314   key = ref[:key]
315   klass = ref.associated_class
316   pk = klass.primary_key
317   label = klass.send(:singularize, ref[:name])
318 
319   field = if ref[:type] == :pg_array_to_many
320     key
321   else
322     "#{label}_pks"
323   end
324   handle_errors(field)
325 
326   unless opts.has_key?(:key)
327     opts[:array] = true unless opts.has_key?(:array)
328     opts[:key] = field
329   end
330   unless opts.has_key?(:value)
331     opts[:value] = obj.respond_to?(field) ? obj.send(field) : obj.send(ref[:name]).map{|x| x.send(pk)}
332   end
333   opts[:options] = association_select_options(ref) unless opts.has_key?(:options)
334   handle_label(label)
335   if opts.delete(:as) == :checkbox
336     _input(:checkboxset, opts)
337   else
338     opts[:multiple] = true unless opts.has_key?(:multiple)
339     _input(:select, opts)
340   end
341 end
association_pg_array_to_many(ref)
association_select_options(ref) click to toggle source

Return an array of two element arrays representing the select options that should be created.

    # File lib/sequel/plugins/forme.rb
347 def association_select_options(ref)
348   case ds = opts[:dataset]
349   when nil
350     ds = association_select_options_dataset(ref)
351   when Proc, Method
352     ds = ds.call(association_select_options_dataset(ref))
353   end
354   rows = ds.all
355 
356   case name_method = forme_name_method(ref)
357   when Symbol, String
358     rows.map{|a| [a.send(name_method), a.pk]}
359   else
360     rows.map{|a| [name_method.call(a), a.pk]}
361   end
362 end
association_select_options_dataset(ref) click to toggle source

The dataset to use to retrieve the association select options

    # File lib/sequel/plugins/forme.rb
365 def association_select_options_dataset(ref)
366   obj.send(:_apply_association_options, ref, ref.associated_class.dataset.clone).unlimited
367 end
forme_name_method(ref) click to toggle source

If the :name_method option is provided, use that as the method. Otherwise, pick the first method in FORME_NAME_METHODS that the associated class implements and use it. If none of the methods are implemented by the associated class, raise an Error.

    # File lib/sequel/plugins/forme.rb
272 def forme_name_method(ref)
273   if meth = opts.delete(:name_method)
274     meth
275   else
276     meths = FORME_NAME_METHODS & ref.associated_class.instance_methods.map(&:to_sym)
277     if meths.empty?
278       raise Error, "No suitable name method found for association #{ref[:name]}"
279     else
280       meths.first
281     end
282   end
283 end
handle_errors(f) click to toggle source

Set the error option correctly if the field contains errors

    # File lib/sequel/plugins/forme.rb
215 def handle_errors(f)
216   return if opts.has_key?(:error)
217   if e = obj.errors.on(f)
218     opts[:error] = e.join(', ')
219   end
220 end
handle_label(f) click to toggle source

Set the label option appropriately, adding a * if the field is required.

    # File lib/sequel/plugins/forme.rb
224 def handle_label(f)
225   opts[:label] = humanize(field) unless opts.has_key?(:label)
226   opts[:label] = [opts[:label], form._tag(:abbr, {:title=>'required'}, '*')] if opts[:label] && opts[:required] && obj.forme_use_required_abbr?
227 end
handle_validations(f) click to toggle source

Update the attributes and options for any recognized validations

    # File lib/sequel/plugins/forme.rb
230 def handle_validations(f)
231   m = obj.model
232 
233   if m.respond_to?(:validation_reflections) and (vs = m.validation_reflections[f])
234     attr = opts[:attr]
235     vs.each do |type, options|
236       attr[:placeholder] = options[:placeholder] if options[:placeholder] && !attr.has_key?(:placeholder)
237 
238       case type
239       when :format
240         attr[:pattern] = options[:with].source unless attr.has_key?(:pattern)
241         attr[:title] = options[:title] unless attr.has_key?(:title)
242       when :length
243         unless attr.has_key?(:maxlength)
244           if max = (options[:maximum] || options[:is])
245             attr[:maxlength] = max
246           elsif (w = options[:within]) && w.is_a?(Range)
247             attr[:maxlength] = if w.exclude_end? && w.end.is_a?(Integer)
248               w.end - 1
249             else
250               w.end
251             end
252           end
253         end
254       when :numericality
255         unless attr.has_key?(:pattern)
256           attr[:pattern] = if options[:only_integer]
257             "^[+\\-]?\\d+$"
258           else
259             "^[+\\-]?\\d+(\\.\\d+)?$"
260           end
261         end
262         attr[:title] = options[:title] || "must be a number" unless attr.has_key?(:title)
263       end
264     end
265   end
266 end
humanize(s) click to toggle source

Delegate to the form.

    # File lib/sequel/plugins/forme.rb
370 def humanize(s)
371   form.respond_to?(:humanize) ? form.humanize(s) : s.to_s.gsub(/_id$/, "").gsub(/_/, " ").capitalize
372 end
input_blob(sch) click to toggle source

Use a file type for blobs.

    # File lib/sequel/plugins/forme.rb
406 def input_blob(sch)
407   input_file(sch)
408 end
input_boolean(sch) click to toggle source

If the column allows NULL values, use a three-valued select input. If not, use a simple checkbox. You can also use :as=>:select, as :as=>:radio, or :as=>:checkbox to specify a particular style.

    # File lib/sequel/plugins/forme.rb
377 def input_boolean(sch)
378   unless opts.has_key?(:as)
379     opts[:as] = (sch[:allow_null] || opts[:required] == false) ? :select : :checkbox
380   end
381 
382   v = opts.has_key?(:value) ? opts[:value] : obj.send(field)
383 
384   case opts[:as]
385   when :radio
386     true_value = opts[:true_value]||'t'
387     false_value = opts[:false_value]||'f'
388     opts[:options] = [[opts[:true_label]||'Yes', {:value=>true_value, :key_id=>'yes'}], [opts[:false_label]||'No', {:value=>false_value, :key_id=>'no'}]]
389     unless v.nil?
390       opts[:value] = v ? true_value : false_value
391     end
392     _input(:radioset, opts)
393   when :select
394     opts[:value] = (v ? 't' : 'f') unless v.nil?
395     opts[:add_blank] = true unless opts.has_key?(:add_blank)
396     opts[:options] = [[opts[:true_label]||'True', opts[:true_value]||'t'], [opts[:false_label]||'False', opts[:false_value]||'f']]
397     _input(:select, opts)
398   else
399     opts[:checked] = v
400     opts[:value] = 't'
401     _input(:checkbox, opts)
402   end
403 end
input_date(sch) click to toggle source

Use date inputs for dates.

    # File lib/sequel/plugins/forme.rb
451 def input_date(sch)
452   standard_input(:date)
453 end
input_datetime(sch) click to toggle source

Use datetime inputs for datetimes.

    # File lib/sequel/plugins/forme.rb
456 def input_datetime(sch)
457   standard_input(:datetime)
458 end
input_file(sch) click to toggle source

Ignore any default values for file inputs.

    # File lib/sequel/plugins/forme.rb
411 def input_file(sch)
412   opts[:value] = nil
413   standard_input(:file)
414 end
input_hidden(sch) click to toggle source

Use hidden inputs without labels.

    # File lib/sequel/plugins/forme.rb
417 def input_hidden(sch)
418   opts[:label] = nil
419   standard_input(:hidden)
420 end
input_integer(sch) click to toggle source

Use inputmode and pattern attributes for integers.

    # File lib/sequel/plugins/forme.rb
444 def input_integer(sch)
445   opts[:attr][:inputmode] ||= 'numeric'
446   opts[:attr][:pattern] ||= '-?[0-9]*'
447   standard_input(:text)
448 end
input_other(sch) click to toggle source

Use a text input for all other types.

    # File lib/sequel/plugins/forme.rb
461 def input_other(sch)
462   standard_input(:text)
463 end
input_string(sch) click to toggle source

Use the text type by default for other cases not handled.

    # File lib/sequel/plugins/forme.rb
423 def input_string(sch)
424   if opts[:as] == :textarea
425     standard_input(:textarea)
426   else
427     case field.to_s
428     when "password"
429       opts[:value] = nil
430       standard_input(:password)
431     when "email"
432       standard_input(:email)
433     when "phone", "fax"
434       standard_input(:tel)
435     when "url", "uri", "website"
436       standard_input(:url)
437     else
438       standard_input(:text)
439     end
440   end
441 end
standard_input(type) click to toggle source

Allow overriding the given type using the :type option, and set the :value option to the field value unless it is overridden.

    # File lib/sequel/plugins/forme.rb
468 def standard_input(type)
469   type = opts.delete(:type) || type
470   if type.to_s =~ /\A(text|textarea|password|email|tel|url)\z/ && !opts[:attr].has_key?(:maxlength) && (sch = obj.db_schema[field]) && (max_length = sch[:max_length])
471     opts[:attr][:maxlength] = max_length
472   end
473   opts[:value] = obj.send(field) unless opts.has_key?(:value)
474   _input(type, opts)
475 end