lib/active_scaffold/finder.rb in active_scaffold-3.6.0.pre vs lib/active_scaffold/finder.rb in active_scaffold-3.6.0.rc1

- old
+ new

@@ -1,9 +1,9 @@ module ActiveScaffold module Finder def self.like_operator - @@like_operator ||= ::ActiveRecord::Base.connection.adapter_name == 'PostgreSQL' ? 'ILIKE' : 'LIKE' + @@like_operator ||= ::ActiveRecord::Base.connection.adapter_name.in?(%w[PostgreSQL PostGIS]) ? 'ILIKE' : 'LIKE' end module ClassMethods def self.extended(klass) return unless klass.active_scaffold_config @@ -66,11 +66,13 @@ conditions = tokens.map do |columns_token| token_conditions = columns.map do |column| value = columns_token[column.name] value = /#{value}/ if column.text? column.search_sql.map do |search_sql| - {search_sql => value} + # call .to_s so String is returned from CowProxy::String in threadsafe mode + # in other case, or method from Mongoid would fail + {search_sql.to_s => value} end end.flatten active_scaffold_config.model.or(token_conditions).selector end [active_scaffold_config.model.and(conditions).selector] @@ -99,16 +101,14 @@ search_ui = column.search_ui || column.column_type begin sql, *values = if search_ui && respond_to?("condition_for_#{search_ui}_type") send("condition_for_#{search_ui}_type", column, value, like_pattern) + elsif column.search_sql.instance_of? Proc + column.search_sql.call(value) else - if column.search_sql.instance_of? Proc - column.search_sql.call(value) - else - condition_for_search_ui(column, value, like_pattern, search_ui) - end + condition_for_search_ui(column, value, like_pattern, search_ui) end return nil unless sql conditions = [column.search_sql.collect { |search_sql| sql % {:search_sql => search_sql} }.join(' OR ')] conditions += values * column.search_sql.size if values.present? @@ -174,16 +174,18 @@ ["%<search_sql>s #{value[:opt]} ?", value[:from]] end end def tables_for_translating_days_and_months(format) + # rubocop:disable Style/FormatStringToken keys = { '%A' => 'date.day_names', '%a' => 'date.abbr_day_names', '%B' => 'date.month_names', '%b' => 'date.abbr_month_names' } + # rubocop:enable Style/FormatStringToken key_index = keys.keys.map { |key| [key, format.index(key)] }.to_h keys.select! { |k, _| key_index[k] } keys.sort_by { |k, _| key_index[k] }.map do |_, k| I18n.t(k).compact.zip(I18n.t(k, :locale => :en).compact).to_h end @@ -224,49 +226,77 @@ format.gsub!(/.*(?=%H)/, '') if !parts[:year] && !parts[:month] && !parts[:mday] [format, parts[:offset]] end + def local_time_from_hash(value, conversion = :to_time) + time = Time.zone.local(*%i[year month day hour minute second].collect { |part| value[part].to_i }) + time.send(conversion) + rescue StandardError => e + message = "Error creating time from #{value.inspect}:" + Rails.logger.warn "#{message}\n#{e.message}\n#{e.backtrace.join("\n")}" + nil + end + + def parse_date_with_format(value, format_name) + format = I18n.t("date.formats.#{format_name || :default}") + format.gsub!(/%-d|%-m|%_m/) { |s| s.gsub(/[-_]/, '') } # strptime fails with %-d, %-m, %_m + en_value = I18n.locale == :en ? value : translate_days_and_months(value, format) + Date.strptime(en_value, format) + rescue StandardError => e + message = "Error parsing date from #{en_value}" + message << " (#{value})" if en_value != value + message << ", with format #{format}" if format + Rails.logger.warn "#{message}:\n#{e.message}\n#{e.backtrace.join("\n")}" + nil + end + + def parse_time_with_format(value, format, offset) + format.gsub!(/%-d|%-m|%_m/) { |s| s.gsub(/[-_]/, '') } # strptime fails with %-d, %-m, %_m + en_value = I18n.locale == :en ? value : translate_days_and_months(value, format) + time = Time.strptime(en_value, format) + offset ? time : Time.zone.local_to_utc(time).in_time_zone + rescue StandardError => e + message = "Error parsing time from #{en_value}" + message << " (#{value})" if en_value != value + message << ", with format #{format}" if format + Rails.logger.warn "#{message}:\n#{e.message}\n#{e.backtrace.join("\n")}" + nil + end + def condition_value_for_datetime(column, value, conversion = :to_time) - unless value.nil? || value.blank? - if value.is_a? Hash - time = Time.zone.local(*%i[year month day hour minute second].collect { |part| value[part].to_i }) rescue nil - time&.send(conversion) - elsif value.respond_to?(:strftime) - if conversion == :to_time - # Explicitly get the current zone, because TimeWithZone#to_time in rails 3.2.3 returns UTC. - # https://github.com/rails/rails/pull/2453 - value.to_time.in_time_zone - else - value.send(conversion) - end - elsif conversion == :to_date - format = I18n.t("date.formats.#{column.options[:format] || :default}") - format.gsub!(/%-d|%-m|%_m/) { |s| s.gsub(/[-_]/, '') } # strptime fails with %-d, %-m, %_m - value = translate_days_and_months(value, format) if I18n.locale != :en - Date.strptime(value, format) rescue nil - elsif value.include?('T') - Time.zone.parse(value) - else # datetime - format, offset = format_for_datetime(column, value) - format.gsub!(/%-d|%-m|%_m/) { |s| s.gsub(/[-_]/, '') } # strptime fails with %-d, %-m, %_m - value = translate_days_and_months(value, format) if I18n.locale != :en - time = Time.strptime(value, format) rescue nil - if time - time = Time.zone.local_to_utc(time).in_time_zone unless offset - time = time.send(conversion) unless conversion == :to_time - end - time + return if value.nil? || value.blank? + if value.is_a? Hash + local_time_from_hash(value, conversion) + elsif value.respond_to?(:strftime) + if conversion == :to_time + # Explicitly get the current zone, because TimeWithZone#to_time in rails 3.2.3 returns UTC. + # https://github.com/rails/rails/pull/2453 + value.to_time.in_time_zone + else + value.send(conversion) end + elsif conversion == :to_date + parse_date_with_format(value, column.options[:format]) + elsif value.include?('T') + Time.zone.parse(value) + else # datetime + time = parse_time_with_format(value, *format_for_datetime(column, value)) + conversion == :to_time ? time : time.send(conversion) end end def condition_value_for_numeric(column, value) return value if value.nil? value = column.number_to_native(value) if column.options[:format] && column.search_ui != :number case (column.search_ui || column.column.type) - when :integer then value.to_i rescue value ? 1 : 0 + when :integer then + if value.is_a?(TrueClass) || value.is_a?(FalseClass) + value ? 1 : 0 + else + value.to_i + end when :float then value.to_f when :decimal ::ActiveRecord::Type::Decimal.new.type_cast_from_user(value) else value @@ -360,14 +390,16 @@ attr_writer :active_scaffold_references def active_scaffold_references @active_scaffold_references ||= [] end - # Override this method on your controller to define conditions to be used when querying a recordset (e.g. for List). The return of this method should be any format compatible with the :conditions clause of ActiveRecord::Base's find. + # Override this method on your controller to define conditions to be used when querying a recordset (e.g. for List). + # The return of this method should be any format compatible with the :conditions clause of ActiveRecord::Base's find. def conditions_for_collection; end - # Override this method on your controller to define joins to be used when querying a recordset (e.g. for List). The return of this method should be any format compatible with the :joins clause of ActiveRecord::Base's find. + # Override this method on your controller to define joins to be used when querying a recordset (e.g. for List). + # The return of this method should be any format compatible with the :joins clause of ActiveRecord::Base's find. def joins_for_collection; end # Override this method on your controller to provide custom finder options to the find() call. The return of this method should be a hash. def custom_finder_options {} @@ -401,19 +433,22 @@ raise ActiveScaffold::RecordNotAllowed, "#{klass} with id = #{id}" unless record.authorized_for? security_options record end # valid options may include: - # * :sorting - a Sorting DataStructure (basically an array of hashes of field => direction, e.g. [{:field1 => 'asc'}, {:field2 => 'desc'}]). please note that multi-column sorting has some limitations: if any column in a multi-field sort uses method-based sorting, it will be ignored. method sorting only works for single-column sorting. + # * :sorting - a Sorting DataStructure (basically an array of hashes of field => direction, + # e.g. [{:field1 => 'asc'}, {:field2 => 'desc'}]). + # please note that multi-column sorting has some limitations: if any column in a multi-field + # sort uses method-based sorting, it will be ignored. method sorting only works for single-column sorting. # * :per_page # * :page def finder_options(options = {}) search_conditions = all_conditions # create a general-use options array that's compatible with Rails finders finder_options = { - :reorder => options[:sorting]&.clause((grouped_columns_calculations if grouped_search?)), + :reorder => options[:sorting]&.clause((grouped_columns_calculations if grouped_search?)).map(&Arel.method(:sql)), :conditions => search_conditions } if active_scaffold_config.mongoid? finder_options[:includes] = [active_scaffold_references, active_scaffold_preload].compact.flatten.uniq.presence else @@ -478,11 +513,11 @@ end pager.page(options[:page]) end def calculate_last_modified(query) - return unless conditional_get_support? && query.klass.columns_hash['updated_at'] + return unless conditional_get_support? && ActiveScaffold::OrmChecks.columns_hash(query.klass)['updated_at'] @last_modified = query.maximum(:updated_at) end def calculate_query conditions = all_conditions @@ -495,16 +530,16 @@ subquery = subquery.unscope(:order) active_scaffold_config.model.where(primary_key => subquery) end def append_to_query(relation, options) - options.assert_valid_keys :where, :select, :having, :group, :reorder, :order, :limit, :offset, :joins, :left_joins, :left_outer_joins, :includes, :lock, :readonly, :from, :conditions, :preload, :references + options.assert_valid_keys :where, :select, :having, :group, :reorder, :order, :limit, :offset, + :joins, :left_joins, :left_outer_joins, :includes, :lock, :readonly, + :from, :conditions, :preload, :references relation = options.reject { |_, v| v.blank? }.inject(relation) do |rel, (k, v)| k == :conditions ? apply_conditions(rel, *v) : rel.send(k, v) end - if options[:left_outer_joins].present? || options[:left_joins].present? - relation.distinct_value = true - end + relation.distinct_value = true if options[:left_outer_joins].present? || options[:left_joins].present? relation end def joins_for_finder case joins_for_collection