module AutoAdmin module ThemeHelpers def view_directory directory 'views' end def asset_root directory 'public' end def controller_includes *includes, &proc @controller_includes ||= [] includes.each do |mod| @controller_includes << mod end if block_given? @controller_includes << Module.new(&proc) end @controller_includes end def helpers @helpers || [] end def helper *helpers, &proc @helpers ||= [] helpers.each do |helper| @helpers << helper end if block_given? @helpers << Module.new(&proc) end end end module TableBuilder def outer; %(); end def prologue; @header = true; %(); end def end_prologue; @header = false; %(); end def epilogue; %(); end def end_outer; %(
); end def fieldset(style, title=nil) @header ? '' : %() end def end_fieldset @header ? '' : %() end def table_header(field_type, field_name, options) content_tag('th', yield, options[:attributes] || {}) end def table_cell(field_type, field_name, options) content_tag('td', yield, options[:attributes] || {}) end def wrap_field(field_type, field_name, options, &block) if @header table_header( field_type, field_name, options ) do || label_text( field_name, options ) end else table_cell( field_type, field_name, options, &block ) end end #def self.append_features base # # I don't think I actually need this cleverness... # instance_methods(false).each do |meth| # base.class_eval <<-end_src, __FILE__, __LINE__ # alias :shadow_#{meth} :#{meth} # end_src # end # super #end end def self.TableBuilder(form_builder) klass = Class.new(form_builder) klass.send :include, TableBuilder klass end module AutoFieldTypeSelector # TODO: We need to provide a facility for registration of automatic # field handlers -- esp. for composed_of, but maybe also for some # belongs_to (in both cases, based on the class, presumably) def auto_field(field, read_only = false) field_type = macro_type(field) if read_only static_text field else case field_type when :belongs_to #, :has_one select field # when :has_and_belongs_to_many #, :has_many # select field, :multiple => true, :size => 7 when :has_and_belongs_to_many, :has_many, :has_one # Until I work out a better strategy, we skip these. when :text text_area field when :string text_field field when :boolean radio_group field when :date date_select field when :datetime datetime_select field else # Don't know how to handle this column type, so we'll just use a # standard text field, but we'll add a (completely non-standard) # HTML attribute of 'unknown', so that looking at the source # will help to reveal what's going on. text_field field, :unknown => field_type end end end end module PrivateFormHelpers private DEFAULT_OPTIONS = { :sort_key => :sort, :link => 1 }.freeze def option(opt_name) (@options && @options.include?( opt_name )) ? @options[opt_name] : DEFAULT_OPTIONS[opt_name] end def model klass = @object ? @object.class : option(:model) raise ArgumentError, "Unable to locate model" unless klass klass end def model_name model.name.underscore end def find_choices(field, options) column = model.find_column( field ) assoc = model.reflect_on_association( field.to_sym ) macro = ( assoc && assoc.macro ) || ( column && column.type ) # If we were given a choice set, handle it choices = options.delete(:choices) if macro == :boolean && choices.is_a?( Array ) && choices.size == 2 && choices.all? {|c| c.is_a? String } # Special case this one for API simplicity return [[choices.first, true], [choices.last, false]] end return choices.to_a if Hash === choices return choices if choices.respond_to? :each raise "Expected Array or Hash as :choices" unless choices.nil? # We haven't been explicitely told what to do, so we guess the # applicable choices case macro when :boolean [['True', true], ['False', false]] when :belongs_to, :has_and_belongs_to_many, :has_many, :has_one assoc.klass.find(:all).map {|o| [o.to_label, o.id] } else # FIXME: This situation is hit for has_enumerated #raise "Don't know how to find choices for #{field} (#{macro.inspect})" [] end end def get_option(options, option_name, object, default_value) result = default_value if options[option_name] result = options[option_name] result = result.call( object ) if result.respond_to? :call end result end def common_option_translations!(options) classes = (options[:class] || '').split classes << 'required' if options.delete( :required ) options[:class] = classes.join(' ') options[:size] ||= 7 if options[:multiple] end def get_column_from_field(field) assoc = model.reflect_on_association( field.to_sym ) assoc ? ([:has_many, :has_and_belongs_to_many].include?(assoc.macro) ? "#{field.to_s.singularize}_ids" : assoc.primary_key_name) : field end def macro_type(column_name) column = model.find_column( column_name ) assoc = model.reflect_on_association( column_name.to_sym ) return ( assoc && assoc.macro ) || ( column && column.type ) end end class BaseFormBuilder < ActionView::Helpers::FormBuilder include AutoFieldTypeSelector include PrivateFormHelpers def none_string(options) '(none)' end def helpers @template end def h(string) helpers.send :h, string end def link_to(*a) helpers.send :link_to, *a end def field_invalid?(field) column = get_column_from_field(field) @object.errors.invalid? column end def field_errors(field) column = get_column_from_field(field) [@object.errors[column]].flatten end private :none_string, :helpers, :h, :link_to, :field_invalid?, :field_errors def field_value(field) @object.send( get_column_from_field( field ) ) unless @object.nil? end private :field_value def object_helper(helper_name, field, options, *extra) @template.send(helper_name, @object_name, field, options.merge( :object => @object ), *extra) end private :object_helper def inner_fields_for(inner_object_name, inner_object) @template.fields_for( "#{@object_name}_#{inner_object_name}", inner_object, @template, @options ) do |i| yield i end end def table_fields_for(inner_object_name, inner_object, extra_options={}, &proc) options = @options.dup options[:inner_builder] = options.delete(:table_builder) options[:binding] ||= @proc.binding options.update extra_options @template.fields_for( "#{@object_name}_#{inner_object_name}", inner_object, @template, options ) do |i| yield i end end def with_object(object, object_name=@object_name) previous_object, @object = @object, object previous_object_name, @object_name = @object_name, object_name yield @object, @object_name = previous_object, previous_object_name @object end def hidden_field(field, options = {}) common_option_translations! options super end def date_select(field, options = {}) common_option_translations! options super end def datetime_select(field, options = {}) common_option_translations! options super end def text_field_with_auto_complete(field, options = {}) common_option_translations! options completion_options = options.delete(:completion) || {} raise "AutoAdmin text_field_with_auto_complete requires explicit :completion => { :url => .. }" unless completion_options.key? :url object_helper :text_field_with_auto_complete, field, options, completion_options end def text_field(field, options = {}) common_option_translations! options super end def text_area(field, options = {}) common_option_translations! options super end def html_area(field, options = {}) common_option_translations! options end def select(field, options = {}, html_options = {}) common_option_translations! options dropdown_options = find_choices(field, options) column = get_column_from_field(field) options[:selected] = field_value( field ) %(class).map {|k| k.to_sym }.each do |k| html_options[k] = options.delete( k ) if options.include?( k ) end super( field, dropdown_options, options, html_options ) end def multiselect(field, options = {}, html_options = {}) common_option_translations! options dropdown_options = find_choices(field, options) column = get_column_from_field(field) html_defaults = { :size => 8, :multiple => true, :name => "#{@object_name}[#{column}][]", :id => "#{@object_name}_#{column}" } helpers.content_tag('select', helpers.options_for_select( dropdown_options, @object && @object.send(column) ), html_defaults.merge( html_options ) ) end def radio_group(field, options = {}) common_option_translations! options choices = find_choices(field, options) choices = choices.to_a if choices.is_a? Hash value = field_value( field ) combine_radio_buttons(choices.map do |choice| opts = options.dup # FIXME: This should be setting :checked to true or false, # which'll work on edge rails, but not 1.1. opts.update( :checked => 'checked' ) if value.to_s == choice.last.to_s radio_button( field, choice.last, opts ) + " " + h( choice.first ) end) end def check_box(field, options = {}, checked_value = '1', unchecked_value = '0') common_option_translations! options super end def hyperlink(field, options = {}) value = @object.send( field ) return none_string( options ) if value.nil? caption = get_option( options, :link_text, value, h( value.to_label ) ) url = get_option( options, :url, value, { :controller => 'auto_admin', :action => 'edit', :model => value.class.name.underscore, :id => value.id } ) link_to( caption, url ) end def file_field(field, options = {}) end def image_field(field, options = {}) end def secure_password(field, options = {}) end def static_image(field, options = {}) end def static_file(field, options = {}) hyperlink field, options end def static_text(field, options = {}) v = @object.send(field) h( v.nil? ? v : v.to_label ) end def calculated_text(options = {}) # :yields: object v = yield @object h( v.nil? ? v : v.to_label ) end def static_html(field, options = {}) @object.send field end def calculated_html(options = {}) # :yields: object yield @object end def html(content = nil, options = {}) # :yields: object raise ArgumentError, "Missing block or field name" unless content || block_given? block_given? ? yield( @object ) : content end def self.calculated_helpers BaseFormBuilder.public_instance_methods(false).select {|m| m =~ /^calculated_/ } end def self.field_helpers methods = BaseFormBuilder.public_instance_methods(false) - calculated_helpers - %w(html auto_field with_object inner_fields_for table_fields_for) ends = methods.select {|m| m =~ /^end_/ } begins = ends.map {|m| m.sub /^end_/, '' } methods - begins - ends end def label_text(field_name, options) options[:label] || model.column_label( field_name ) end private :label_text def combine_radio_buttons(items) items.join ' ' end private :combine_radio_buttons def outer; %(); end def prologue; %(); end def end_prologue; %(); end def epilogue; %(); end def end_epilogue; %(); end def end_outer; %(); end end end module AutoAdminSimpleTheme extend AutoAdmin::ThemeHelpers def self.directory(*subdirs) raise "Can't use 'simple' theme; it's just an abstract base for other themes." end # This FormProcessor defines all the standard field helpers; they're # just calls to common_field_translations! (which calls # translate_association_to_column!), because everything else about the # standard field helpers is handled by the standard save action. class FormProcessor include AutoAdmin::AutoFieldTypeSelector include AutoAdmin::PrivateFormHelpers def table_fields_for(inner_object_name, inner_object, extra_options={}, &proc) options = @options.dup options.update extra_options if table_params = @controller.params[inner_object_name] table_params.each do |row_number, row_params| if row_params yield self.class.new( inner_object, inner_object_name, extra_options[:model], @controller, row_params, options ) end end end end def with_object(object, object_name=@object_name) previous_object, @object = @object, object previous_object_name, @object_name = @object_name, object_name yield @object, @object_name = previous_object, previous_object_name @object end attr_accessor :object, :object_name, :model, :controller, :params, :options def initialize(object, object_name, model, controller, params, options={}) @object, @object_name, @model, @controller, @params, @options = object, object_name, model, controller, params, options end # Calculated helpers are always read-only, so they needn't do # anything in a FormProcessor. AutoAdmin::BaseFormBuilder.calculated_helpers.each do |helper| class_eval <<-end_src, __FILE__, __LINE__ def #{helper}(options={}); end end_src end AutoAdmin::BaseFormBuilder.field_helpers.each do |helper| class_eval <<-end_src, __FILE__, __LINE__ def #{helper}(field, options={}, *args, &proc) common_field_translations! field end end_src end def nullable(field_type, field, options={}) check_for_nullable_field! field common_field_translations! field end %w(outer prologue epilogue).each do |helper| class_eval <<-end_src, __FILE__, __LINE__ def #{helper}; yield if block_given?; end def end_#{helper}; end end_src end def fieldset(style, title=nil); yield if block_given?; end def end_fieldset; end def common_field_translations!(field_name) return unless params.include? field_name translate_association_to_column! field_name end def check_for_nullable_field!(field_name) params[field_name] = nil if params.delete( "#{field_name}_NULL" ) == 'NULL' end def translate_association_to_column!(field_name) column = get_column_from_field( field_name ) return if column.to_s == field_name.to_s params[column] = params.delete( field_name ) end end class FormBuilder < AutoAdmin::BaseFormBuilder def wrap_field(field_type, field_name, options) label = label_text( field_name, options ) klass = options[:required] ? 'required' : '' %( #{yield}
) end def fieldset_class(style) case style when :fields then 'fields' end end def fieldset(style, title=nil) %(
) + (title ? %(

#{h title}

) : '') end def end_fieldset %(
) end def nullable(field_type, field_name, options={}) wrap_field field_type, field_name, options do |*a| null_label = options.delete(:null_label) || 'None' standard_field = send("#{field_type}_without_theme", field_name, options) is_null = field_value(field_name).nil? <<-foo #{null_label}
#{standard_field} foo end end calculated_helpers.each do |helper| class_eval <<-end_src, __FILE__, __LINE__ alias :#{helper}_without_theme :#{helper} def #{helper}(options={}, *args, &proc) wrap_field #{helper.to_sym.inspect}, nil, options do |*a| a.empty? ? #{helper}_without_theme(options, *args, &proc) : #{helper}_without_theme(*a) end end end_src end (field_helpers - %w(hidden_field)).each do |helper| class_eval <<-end_src, __FILE__, __LINE__ alias :#{helper}_without_theme :#{helper} def #{helper}(field, options={}, *args, &proc) wrap_field #{helper.to_sym.inspect}, field, options do |*a| a.empty? ? #{helper}_without_theme(field, options, *args, &proc) : #{helper}_without_theme(*a) end end end_src end end class TableBuilder < AutoAdmin::TableBuilder(FormBuilder) end end