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
The field/column name related to the receiver. The type of input created usually depends upon this field.
The form related to the receiver.
The Sequel::Model
object related to the receiver.
The options hash related to the receiver.
Public Class Methods
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
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
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
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
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
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
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
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
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
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
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
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
Use a file type for blobs.
# File lib/sequel/plugins/forme.rb 406 def input_blob(sch) 407 input_file(sch) 408 end
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
Use date inputs for dates.
# File lib/sequel/plugins/forme.rb 451 def input_date(sch) 452 standard_input(:date) 453 end
Use datetime inputs for datetimes.
# File lib/sequel/plugins/forme.rb 456 def input_datetime(sch) 457 standard_input(:datetime) 458 end
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
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
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
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
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