class Hobo::Model::Scopes::ScopeBuilder

The methods on this module add scopes to the given class

Attributes

name[R]

Public Instance Methods

column(name) click to toggle source
# File lib/hobo/model/scopes/automatic_scopes.rb, line 401
def column(name)
  @klass.column(name)
end
column_sql(column) click to toggle source
# File lib/hobo/model/scopes/automatic_scopes.rb, line 354
def column_sql(column)
  "#{@klass.table_name}.#{column.name}"
end
create_scope(check_only=false) click to toggle source
# File lib/hobo/model/scopes/automatic_scopes.rb, line 31
def create_scope(check_only=false)
  matched_scope = true

  like_operator = ActiveRecord::Base.connection.adapter_name =~ /postg/ ? 'ILIKE' : 'LIKE'

  case
  # --- Association Queries --- #

  # with_players(player1, player2)
  when name =~ /^with_(.*)/ && (refl = reflection($1))
    return true if check_only

    def_scope do |*records|
      if records.empty?
        @klass.where exists_sql_condition(refl, true)
      else
        records = records.flatten.compact.map {|r| find_if_named(refl, r) }
        exists_sql = ([exists_sql_condition(refl)] * records.length).join(" AND ")
        @klass.where *([exists_sql] + records)
      end
    end

  # with_player(a_player)
  when name =~ /^with_(.*)/ && (refl = reflection($1.pluralize))
    return true if check_only

    exists_sql = exists_sql_condition(refl)
    def_scope do |record|
      record = find_if_named(refl, record)
      @klass.where exists_sql, record
    end

  # any_of_players(player1, player2)
  when name =~ /^any_of_(.*)/ && (refl = reflection($1))
    return true if check_only

    def_scope do |*records|
      if records.empty?
        @klass.where exists_sql_condition(refl, true)
      else
        records = records.flatten.compact.map {|r| find_if_named(refl, r) }
        exists_sql = ([exists_sql_condition(refl)] * records.length).join(" OR ")
        @klass.where *([exists_sql] + records)
      end
    end

  # without_players(player1, player2)
  when name =~ /^without_(.*)/ && (refl = reflection($1))
    return true if check_only

    def_scope do |*records|
      if records.empty?
        @klass.where "NOT (#{exists_sql_condition(refl, true)})"
      else
        records = records.flatten.compact.map {|r| find_if_named(refl, r) }
        exists_sql = ([exists_sql_condition(refl)] * records.length).join(" AND ")
        @klass.where *(["NOT (#{exists_sql})"] + records)
      end
    end

  # without_player(a_player)
  when name =~ /^without_(.*)/ && (refl = reflection($1.pluralize))
    return true if check_only

    exists_sql = exists_sql_condition(refl)
    def_scope do |record|
      record = find_if_named(refl, record)
      @klass.where "NOT #{exists_sql}", record
    end

  # team_is(a_team)
  when name =~ /^(.*)_is$/ && (refl = reflection($1)) && refl.macro.in?([:has_one, :belongs_to])
    return true if check_only

    if refl.options[:polymorphic]
      def_scope do |record|
        record = find_if_named(refl, record)
        @klass.where "#{foreign_key_column refl} = ? AND #{$1}_type = ?", record, record.class.name
      end
    else
      def_scope do |record|
        record = find_if_named(refl, record)
        @klass.where "#{foreign_key_column refl} = ?", record
      end
    end

  # team_is_not(a_team)
  when name =~ /^(.*)_is_not$/ && (refl = reflection($1)) && refl.macro.in?([:has_one, :belongs_to])
    return true if check_only

    if refl.options[:polymorphic]
      def_scope do |record|
        record = find_if_named(refl, record)
        @klass.where "#{foreign_key_column refl} <> ? OR #{name}_type <> ?", record, record.class.name
      end
    else
      def_scope do |record|
        record = find_if_named(refl, record)
        @klass.where "#{foreign_key_column refl} <> ?", record
      end
    end


  # --- Column Queries --- #

  # name_is(str)
  when name =~ /^(.*)_is$/ && (col = column($1))
    return true if check_only

    def_scope do |str|
      @klass.where "#{column_sql(col)} = ?", str
    end

  # name_is_not(str)
  when name =~ /^(.*)_is_not$/ && (col = column($1))
    return true if check_only

    def_scope do |str|
      @klass.where "#{column_sql(col)} <> ?", str
    end

  # name_contains(str)
  when name =~ /^(.*)_contains$/ && (col = column($1))
    return true if check_only

    def_scope do |str|
      @klass.where "#{column_sql(col)} #{like_operator} ?", "%#{str}%"
    end

  # name_does_not_contain
  when name =~ /^(.*)_does_not_contain$/ && (col = column($1))
    return true if check_only

    def_scope do |str|
      @klass.where "#{column_sql(col)} NOT #{like_operator} ?", "%#{str}%"
    end

  # name_starts(str)
  when name =~ /^(.*)_starts$/ && (col = column($1))
    return true if check_only

    def_scope do |str|
      @klass.where "#{column_sql(col)} #{like_operator} ?", "#{str}%"
    end

  # name_does_not_start
  when name =~ /^(.*)_does_not_start$/ && (col = column($1))
    return true if check_only

    def_scope do |str|
      @klass.where "#{column_sql(col)} NOT #{like_operator} ?", "#{str}%"
    end

  # name_ends(str)
  when name =~ /^(.*)_ends$/ && (col = column($1))
    return true if check_only

    def_scope do |str|
      @klass.where "#{column_sql(col)} #{like_operator} ?", "%#{str}"
    end

  # name_does_not_end(str)
  when name =~ /^(.*)_does_not_end$/ && (col = column($1))
    return true if check_only

    def_scope do |str|
      @klass.where "#{column_sql(col)} NOT #{like_operator} ?", "%#{str}"
    end

  # published (a boolean column)
  when (col = column(name)) && (col.type == :boolean)
    return true if check_only

    def_scope do
      @klass.where "#{column_sql(col)} = ?", true
    end

  # not_published
  when name =~ /^not_(.*)$/ && (col = column($1)) && (col.type == :boolean)
    return true if check_only

    def_scope do
      @klass.where "#{column_sql(col)} <> ?", true
    end

  # published_before(time)
  when name =~ /^(.*)_before$/ && (col = column("#{$1}_at") || column("#{$1}_date") || column("#{$1}_on")) && col.type.in?([:date, :datetime, :time, :timestamp])
    return true if check_only

    def_scope do |time|
      @klass.where "#{column_sql(col)} < ?", time
    end

  # published_after(time)
  when name =~ /^(.*)_after$/ && (col = column("#{$1}_at") || column("#{$1}_date") || column("#{$1}_on")) && col.type.in?([:date, :datetime, :time, :timestamp])
    return true if check_only

    def_scope do |time|
      @klass.where "#{column_sql(col)} > ?", time
    end

  # published_between(time1, time2)
  when name =~ /^(.*)_between$/ && (col = column("#{$1}_at") || column("#{$1}_date") || column("#{$1}_on")) && col.type.in?([:date, :datetime, :time, :timestamp])
    return true if check_only

    def_scope do |time1, time2|
      @klass.where "#{column_sql(col)} >= ? AND #{column_sql(col)} <= ?", time1, time2
    end

   # active (a lifecycle state)
  when @klass.has_lifecycle? && name.to_sym.in?(@klass::Lifecycle.state_names)
    return true if check_only

    if @klass::Lifecycle.state_names.length == 1
      # nothing to check for - create a dummy scope
      def_scope { @klass.scoped }
      true
    else
      def_scope do
        @klass.where "#{@klass.table_name}.#{@klass::Lifecycle.state_field} = ?", name
      end
    end

  # self is / is not
  when name == "is"
    return true if check_only

    def_scope do |record|
      @klass.where "#{@klass.table_name}.#{@klass.primary_key} = ?", record
    end

  when name == "is_not"
    return true if check_only

    def_scope do |record|
      @klass.where "#{@klass.table_name}.#{@klass.primary_key} <> ?", record
    end


  when name == "by_most_recent"
    return true if check_only

    def_scope do
      @klass.order "#{@klass.table_name}.created_at DESC"
    end

  when name == "recent"
    return true if check_only

    if "created_at".in?(@klass.columns.*.name)
      def_scope do |*args|
        count = args.first || 6
        @klass.order("#{@klass.table_name}.created_at DESC").limit(count)
      end
    else
      def_scope do |*args|
        count = args.first || 6
        limit(count)
      end
    end

  when name == "order_by"
    return true if check_only

    klass = @klass
    def_scope do |*args|
      field, asc = args
      field ||= ""
      type = klass.attr_type(field)
      if type.nil? #a virtual attribute from an SQL alias, e.g., 'total' from 'COUNT(*) AS total'
        colspec = "#{field}" # don't prepend the table name
      elsif type.respond_to?(:name_attribute) && (name = type.name_attribute)
        include = field
        colspec = "#{type.table_name}.#{name}"
      else
        colspec = "#{klass.table_name}.#{field}"
      end
      @klass.includes(include).order("#{colspec} #{asc._?.upcase}")
    end

  when name == "include"
    # DEPRECATED: it clashes with Module.include when called on an ActiveRecord::Relation
    # after a scope chain, if you didn't call it on the class itself first
    Rails.logger.warn "Automatic scope :include has been deprecated: use :includes instead."
    return true if check_only

    def_scope do |inclusions|
      @klass.includes(inclusions)
    end

  when name == "search"
    return true if check_only

    def_scope do |query, *fields|
      using_postgresql = %w(PostgreSQL PostGIS).include?(::ActiveRecord::Base.connection.adapter_name)
      match_keyword = using_postgresql ? "ILIKE" : "LIKE"
      words = (query || "").split
      args = []
      word_queries = words.map do |word|
        field_query = '(' + fields.map { |field|
          if using_postgresql
            casted_field = "CAST(#{@klass.table_name}.#{field} AS TEXT)"
          else
            casted_field = "#{@klass.table_name}.#{field}"
          end
          field = "#{casted_field}" unless field =~ /\./
          "(#{field} #{match_keyword} ?)"
        }.join(" OR ") + ')'
        args += ["%#{word}%"] * fields.length
        field_query
      end

      @klass.where *([word_queries.join(" AND ")] + args)
    end

  else
    matched_scope = false
  end

  matched_scope
end
def_scope(&block) click to toggle source
# File lib/hobo/model/scopes/automatic_scopes.rb, line 411
def def_scope(&block)
  @klass.scope name.to_sym, (lambda &block)
end
exists_sql_condition(reflection, any=false) click to toggle source
# File lib/hobo/model/scopes/automatic_scopes.rb, line 359
def exists_sql_condition(reflection, any=false)
  owner = @klass
  owner_primary_key = "#{owner.table_name}.#{owner.primary_key}"

  if reflection.options[:through]
    join_table   = reflection.through_reflection.klass.table_name
    owner_fkey   = reflection.through_reflection.foreign_key
    conditions   = reflection.options[:conditions].blank? ? '' : " AND #{reflection.through_reflection.klass.send(:sanitize_sql_for_conditions, reflection.options[:conditions])}"

    if any
      "EXISTS (SELECT * FROM #{join_table} WHERE #{join_table}.#{owner_fkey} = #{owner_primary_key}#{conditions})"
    else
      source_fkey  = reflection.source_reflection.foreign_key
      "EXISTS (SELECT * FROM #{join_table} " +
        "WHERE #{join_table}.#{source_fkey} = ? AND #{join_table}.#{owner_fkey} = #{owner_primary_key}#{conditions})"
    end
  else
    foreign_key = reflection.foreign_key
    related     = reflection.klass
    conditions = reflection.options[:conditions].blank? ? '' : " AND #{reflection.klass.send(:sanitize_sql_for_conditions, reflection.options[:conditions])}"

    if any
      "EXISTS (SELECT * FROM #{related.table_name} " +
        "WHERE #{related.table_name}.#{foreign_key} = #{owner_primary_key}#{conditions})"
    else
      "EXISTS (SELECT * FROM #{related.table_name} " +
        "WHERE #{related.table_name}.#{foreign_key} = #{owner_primary_key} AND " +
        "#{related.table_name}.#{related.primary_key} = ?#{conditions})"
    end
  end
end
find_if_named(reflection, string_or_record) click to toggle source
# File lib/hobo/model/scopes/automatic_scopes.rb, line 391
def find_if_named(reflection, string_or_record)
  if string_or_record.is_a?(String)
    name = string_or_record
    reflection.klass.named(name)
  else
    string_or_record # a record
  end
end
foreign_key_column(refl) click to toggle source
# File lib/hobo/model/scopes/automatic_scopes.rb, line 421
def foreign_key_column(refl)
  "#{@klass.table_name}.#{refl.foreign_key}"
end
primary_key_column(refl) click to toggle source
# File lib/hobo/model/scopes/automatic_scopes.rb, line 416
def primary_key_column(refl)
  "#{refl.klass.table_name}.#{refl.klass.primary_key}"
end
reflection(name) click to toggle source
# File lib/hobo/model/scopes/automatic_scopes.rb, line 406
def reflection(name)
  @klass.reflections[name.to_s]
end

Public Class Methods

new(klass, name) click to toggle source
# File lib/hobo/model/scopes/automatic_scopes.rb, line 24
def initialize(klass, name)
  @klass = klass
  @name  = name.to_s
end