# frozen_string_literal: true require 'rails_admin/support/datetime' module RailsAdmin class AbstractModel cattr_accessor :all attr_reader :adapter, :model_name class << self def reset @@all = nil end def all(adapter = nil) @@all ||= Config.models_pool.collect { |m| new(m) }.compact adapter ? @@all.select { |m| m.adapter == adapter } : @@all end alias_method :old_new, :new def new(m) m = m.constantize unless m.is_a?(Class) (am = old_new(m)).model && am.adapter ? am : nil rescue *([LoadError, NameError] + (defined?(ActiveRecord) ? ['ActiveRecord::NoDatabaseError'.constantize, 'ActiveRecord::ConnectionNotEstablished'.constantize] : [])) puts "[RailsAdmin] Could not load model #{m}, assuming model is non existing. (#{$ERROR_INFO})" unless Rails.env.test? nil end @@polymorphic_parents = {} def polymorphic_parents(adapter, model_name, name) @@polymorphic_parents[adapter.to_sym] ||= {}.tap do |hash| all(adapter).each do |am| am.associations.select(&:as).each do |association| (hash[[association.klass.to_s.underscore, association.as].join('_').to_sym] ||= []) << am.model end end end @@polymorphic_parents[adapter.to_sym][[model_name.to_s.underscore, name].join('_').to_sym] end # For testing def reset_polymorphic_parents @@polymorphic_parents = {} end end def initialize(model_or_model_name) @model_name = model_or_model_name.to_s ancestors = model.ancestors.collect(&:to_s) if ancestors.include?('ActiveRecord::Base') && !model.abstract_class? && model.table_exists? initialize_active_record elsif ancestors.include?('Mongoid::Document') initialize_mongoid end end # do not store a reference to the model, does not play well with ActiveReload/Rails3.2 def model @model_name.constantize end def to_s model.to_s end def config Config.model self end def to_param @model_name.split('::').collect(&:underscore).join('~') end def param_key @model_name.split('::').collect(&:underscore).join('_') end def pretty_name model.model_name.human end def where(conditions) model.where(conditions) end def each_associated_children(object) associations.each do |association| case association.type when :has_one child = object.send(association.name) yield(association, [child]) if child when :has_many children = object.send(association.name) yield(association, Array.new(children)) end end end private def initialize_active_record @adapter = :active_record if defined?(::CompositePrimaryKeys) require 'rails_admin/adapters/composite_primary_keys' extend Adapters::CompositePrimaryKeys else require 'rails_admin/adapters/active_record' extend Adapters::ActiveRecord end end def initialize_mongoid @adapter = :mongoid require 'rails_admin/adapters/mongoid' extend Adapters::Mongoid end def parse_field_value(field, value) value.is_a?(Array) ? value.map { |v| field.parse_value(v) } : field.parse_value(value) end class StatementBuilder def initialize(column, type, value, operator) @column = column @type = type @value = value @operator = operator end def to_statement return if [@operator, @value].any? { |v| v == '_discard' } unary_operators[@operator] || unary_operators[@value] || build_statement_for_type_generic end protected def get_filtering_duration FilteringDuration.new(@operator, @value).get_duration end def build_statement_for_type_generic build_statement_for_type || begin case @type when :date build_statement_for_date when :datetime, :timestamp, :time build_statement_for_datetime_or_timestamp end end end def build_statement_for_type raise 'You must override build_statement_for_type in your StatementBuilder' end def build_statement_for_integer_decimal_or_float case @value when Array val, range_begin, range_end = *@value.collect do |v| next unless v.to_i.to_s == v || v.to_f.to_s == v @type == :integer ? v.to_i : v.to_f end case @operator when 'between' range_filter(range_begin, range_end) else column_for_value(val) if val end else if @value.to_i.to_s == @value || @value.to_f.to_s == @value @type == :integer ? column_for_value(@value.to_i) : column_for_value(@value.to_f) end end end def build_statement_for_date start_date, end_date = get_filtering_duration if start_date start_date = begin start_date.to_date rescue StandardError nil end end if end_date end_date = begin end_date.to_date rescue StandardError nil end end range_filter(start_date, end_date) end def build_statement_for_datetime_or_timestamp start_date, end_date = get_filtering_duration start_date = start_date.beginning_of_day if start_date.is_a?(Date) end_date = end_date.end_of_day if end_date.is_a?(Date) range_filter(start_date, end_date) end def unary_operators raise 'You must override unary_operators in your StatementBuilder' end def range_filter(_min, _max) raise 'You must override range_filter in your StatementBuilder' end class FilteringDuration def initialize(operator, value) @value = value @operator = operator end def get_duration case @operator when 'between' then between when 'today' then today when 'yesterday' then yesterday when 'this_week' then this_week when 'last_week' then last_week else default end end def today [Date.today, Date.today] end def yesterday [Date.yesterday, Date.yesterday] end def this_week [Date.today.beginning_of_week, Date.today.end_of_week] end def last_week [1.week.ago.to_date.beginning_of_week, 1.week.ago.to_date.end_of_week] end def between [@value[1], @value[2]] end def default [default_date, default_date] end private def default_date Array.wrap(@value).first end end end end end