1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348
|
require 'rails_admin/config/proxyable'
require 'rails_admin/config/configurable'
require 'rails_admin/config/hideable'
require 'rails_admin/config/groupable'
module RailsAdmin
module Config
module Fields
class Base # rubocop:disable ClassLength
include RailsAdmin::Config::Proxyable
include RailsAdmin::Config::Configurable
include RailsAdmin::Config::Hideable
include RailsAdmin::Config::Groupable
attr_reader :name, :properties, :abstract_model
attr_accessor :defined, :order, :section
attr_reader :parent, :root
def initialize(parent, name, properties)
@parent = parent
@root = parent.root
@abstract_model = parent.abstract_model
@defined = false
@name = name.to_sym
@order = 0
@properties = properties
@section = parent
end
register_instance_option :css_class do
"#{self.name}_field"
end
def type_css_class
"#{type}_type"
end
def virtual?
properties.blank?
end
register_instance_option :column_width do
nil
end
register_instance_option :sortable do
!virtual? || children_fields.first || false
end
register_instance_option :searchable do
!virtual? || children_fields.first || false
end
register_instance_option :queryable? do
!virtual?
end
register_instance_option :filterable? do
!!searchable # rubocop:disable DoubleNegation
end
register_instance_option :search_operator do
@search_operator ||= RailsAdmin::Config.default_search_operator
end
# serials and dates are reversed in list, which is more natural (last modified items first).
register_instance_option :sort_reverse? do
false
end
# list of columns I should search for that field [{ column: 'table_name.column', type: field.type }, {..}]
register_instance_option :searchable_columns do
@searchable_columns ||= begin
case searchable
when true
[{column: "#{abstract_model.table_name}.#{name}", type: type}]
when false
[]
when :all # valid only for associations
table_name = associated_model_config.abstract_model.table_name
associated_model_config.list.fields.collect { |f| {column: "#{table_name}.#{f.name}", type: f.type} }
else
[searchable].flatten.collect do |f|
if f.is_a?(String) && f.include?('.') # table_name.column
table_name, column = f.split '.'
type = nil
elsif f.is_a?(Hash) # <Model|table_name> => <attribute|column>
am = f.keys.first.is_a?(Class) && AbstractModel.new(f.keys.first)
table_name = am && am.table_name || f.keys.first
column = f.values.first
property = am && am.properties.detect { |p| p.name == f.values.first.to_sym }
type = property && property.type
else # <attribute|column>
am = (self.association? ? associated_model_config.abstract_model : abstract_model)
table_name = am.table_name
column = f
property = am.properties.detect { |p| p.name == f.to_sym }
type = property && property.type
end
{column: "#{table_name}.#{column}", type: (type || :string)}
end
end
end
end
register_instance_option :formatted_value do
value
end
# output for pretty printing (show, list)
register_instance_option :pretty_value do
formatted_value.presence || ' - '
end
# output for printing in export view (developers beware: no bindings[:view] and no data!)
register_instance_option :export_value do
pretty_value
end
# Accessor for field's help text displayed below input field.
register_instance_option :help do
(@help ||= {})[::I18n.locale] ||= generic_field_help
end
register_instance_option :html_attributes do
{
required: required?,
}
end
register_instance_option :default_value do
nil
end
# Accessor for field's label.
#
# @see RailsAdmin::AbstractModel.properties
register_instance_option :label do
(@label ||= {})[::I18n.locale] ||= abstract_model.model.human_attribute_name name
end
register_instance_option :hint do
(@hint ||= '')
end
# Accessor for field's maximum length per database.
#
# @see RailsAdmin::AbstractModel.properties
register_instance_option :length do
@length ||= properties && properties.length
end
# Accessor for field's length restrictions per validations
#
register_instance_option :valid_length do
@valid_length ||= abstract_model.model.validators_on(name).detect { |v| v.kind == :length }.try(&:options) || {}
end
register_instance_option :partial do
:form_field
end
# Accessor for whether this is field is mandatory.
#
# @see RailsAdmin::AbstractModel.properties
register_instance_option :required? do
context = begin
if bindings && bindings[:object]
bindings[:object].persisted? ? :update : :create
else
:nil
end
end
(@required ||= {})[context] ||= !!([name] + children_fields).uniq.detect do |column_name| # rubocop:disable DoubleNegation
abstract_model.model.validators_on(column_name).detect do |v|
!(v.options[:allow_nil] || v.options[:allow_blank]) &&
[:presence, :numericality, :attachment_presence].include?(v.kind) &&
(v.options[:on] == context || v.options[:on].blank?) &&
(v.options[:if].blank? && v.options[:unless].blank?)
end
end
end
# Accessor for whether this is a serial field (aka. primary key, identifier).
#
# @see RailsAdmin::AbstractModel.properties
register_instance_option :serial? do
properties && properties.serial?
end
register_instance_option :view_helper do
:text_field
end
register_instance_option :read_only? do
!editable?
end
# init status in the view
register_instance_option :active? do
false
end
register_instance_option :visible? do
returned = true
(RailsAdmin.config.default_hidden_fields || {}).each do |section, fields|
next unless self.section.is_a?("RailsAdmin::Config::Sections::#{section.to_s.camelize}".constantize)
returned = false if fields.include?(name)
end
returned
end
# columns mapped (belongs_to, paperclip, etc.). First one is used for searching/sorting by default
register_instance_option :children_fields do
[]
end
register_instance_option :render do
bindings[:view].render partial: "rails_admin/main/#{partial}", locals: {field: self, form: bindings[:form]}
end
def editable?
!(@properties && @properties.read_only?)
end
# Is this an association
def association?
is_a?(RailsAdmin::Config::Fields::Association)
end
# Reader for validation errors of the bound object
def errors
([name] + children_fields).uniq.collect do |column_name|
bindings[:object].errors[column_name]
end.uniq.flatten
end
# Reader whether field is optional.
#
# @see RailsAdmin::Config::Fields::Base.register_instance_option :required?
def optional?
!required?
end
# Inverse accessor whether this field is required.
#
# @see RailsAdmin::Config::Fields::Base.register_instance_option :required?
def optional(state = nil, &block)
if !state.nil? || block # rubocop:disable NonNilCheck
required state.nil? ? proc { false == (instance_eval(&block)) } : false == state
else
optional?
end
end
# Writer to make field optional.
#
# @see RailsAdmin::Config::Fields::Base.optional
def optional=(state)
optional(state)
end
# Reader for field's type
def type
@type ||= self.class.name.to_s.demodulize.underscore.to_sym
end
# Reader for field's value
def value
bindings[:object].safe_send(name)
rescue NoMethodError => e
raise e.exception <<-EOM.gsub(/^\s{10}/, '')
#{e.message}
If you want to use a RailsAdmin virtual field(= a field without corresponding instance method),
you should declare 'formatted_value' in the field definition.
field :#{name} do
formatted_value{ bindings[:object].call_some_method }
end
EOM
end
# Reader for nested attributes
register_instance_option :nested_form do
false
end
# Allowed methods for the field in forms
register_instance_option :allowed_methods do
[method_name]
end
def generic_help
(required? ? I18n.translate('admin.form.required') : I18n.translate('admin.form.optional')) + '. '
end
def generic_field_help
model = abstract_model.model_name.underscore
model_lookup = "admin.help.#{model}.#{name}".to_sym
translated = I18n.translate(model_lookup, help: generic_help, default: [generic_help])
(translated.is_a?(Hash) ? translated.to_a.first[1] : translated).html_safe
end
def parse_value(value)
value
end
def parse_input(_params)
# overriden
end
def inverse_of
nil
end
def method_name
name
end
def form_default_value
(default_value if bindings[:object].new_record? && value.nil?)
end
def form_value
form_default_value.nil? ? formatted_value : form_default_value
end
def inspect
"#<#{self.class.name}[#{name}] #{
instance_variables.collect do |v|
value = instance_variable_get(v)
if [:@parent, :@root, :@section, :@children_fields_registered,
:@associated_model_config, :@group, :@bindings].include? v
if value.respond_to? :name
"#{v}=#{value.name.inspect}"
else
"#{v}=#{value.class.name}"
end
else
"#{v}=#{value.inspect}"
end
end.join(', ')
}>"
end
end
end
end
end
|