3

このrailscastに基づくモデルのスコープを実装しています。&スコープ内の条件は、次のようにバイナリAND演算子を使用します。

scope :with_role, lambda { |role| 
  {:conditions => "roles_mask & #{2**ROLES.index(role.to_s)} > 0"} 
}

現在対象としているデータベースはOracleでありBITAND、演算子の代わりに関数を使用している&ため、条件を次のように書き直しました。

{:conditions => "BITAND(roles_mask, #{2**ROLES.index(role.to_s)}) > 0"}

私の問題は、将来、他のデータベースをターゲットにすることを計画しているため、コードをデータベースにとらわれないようにしたいということです。私の現在の解決策は、Oracleを使用しているかどうかを確認し、それに応じてスコープを次のように定義することです(using_oracle他の場所で計算するブール値です)。

if using_oracle 
  scope :with_role, lambda { |role| 
    {:conditions => "BITAND(roles_mask, #{2**ROLES.index(role.to_s)}) > 0"}
  }
else
  scope :with_role, lambda { |role| 
    {:conditions => "roles_mask & #{2**ROLES.index(role.to_s)} > 0"}
  }
end

これは機能しますが、特にエレガントまたはルビー/レールのようなものとして私を襲うことはありません。誰かが親切にもっと良い代替案を提案できますか?

4

1 に答える 1

2

アレルを拡張するためのより良い方法があるはずだと思います。私はこの結果に到達するのに苦労しました。

ともかく; このソリューションはModel#extendingを使用します:

module BitOperations
  def bitwise_and_sql
    @bitwise_and_sql ||=
      case connection.adapter_name
      when 'Oracle' # probably wrong!
        "BITAND(%s, %s)"
      else
        "%s & %s"
      end
  end
  def bitwise_and(i, j)
    where(bitwise_and_sql % [i, j])
  end
  def bitmask(i, j)
    where('%s > 0' % scoped.bitwise_and(i, j).wheres.to_a.last.to_sql)
  end
end

p User.scoped.extending(BitOperations).bitwise_and(1, 2).bitmask(3, 4).to_sql
#=> "SELECT \"users\".* FROM \"users\"  WHERE (1 & 2) AND ((3 & 4) > 0)"

.wheresアレル関係が含まれています。が含まれてEnumerableいるため、最後のリレーションを取得して配列に変換し、最後の要素を取得できます。で使用するためにのSQLを取得するbitwise_and(i, j)ために使用しましたbitmask(i, j)。どこからSQLを取得するためのより良い方法があるのだろうか...

.wheres非推奨に関する警告が表示wheresされますが、現時点では無視できます(Rails 4ベータ版でも機能します)。

次のクラスメソッドを定義できますUser

class User
  def self.scope_with_bit_operations
    @scope_with_bit_operations ||= scoped.extending(BitOperations)    
  end
  def self.bitwise_and(i, j)
    scope_with_bit_operations.bitwise_and(i, j)
  end
  def self.bitmask(i, j)
    scope_with_bit_operations.bitmask(i, j)
  end
end

p User.bitwise_and(1, 2).bitmask(3, 4).to_sql
#=> "SELECT \"users\".* FROM \"users\"  WHERE (1 & 2) AND ((3 & 4) > 0)"

またはすべてのモデルの場合:

class ActiveRecord::Base
  def self.scope_with_bit_operations
    @scope_with_bit_operations ||= scoped.extending(BitOperations)    
  end
  def self.bitwise_and(i, j)
    scope_with_bit_operations.bitwise_and(i, j)
  end
  def self.bitmask(i, j)
    scope_with_bit_operations.bitmask(i, j)
  end
end

p Post.bitwise_and(1, 2).bitmask(3, 4).to_sql
#=> "SELECT \"posts\".* FROM \"posts\"  WHERE (1 & 2) AND ((3 & 4) > 0)"

そして最後に、もう少しエレガントなwith_roleスコープを実装できます。

class User < ActiveRecord::Base
  ROLES = %w[admin moderator author]

  scope :with_role, ->(role) do
    # I'm a fan of quoting everything :-P
    bitmask connection.quote_column_name(:roles_mask),
            connection.quote(2**ROLES.index(role.to_s))
  end
end

p User.with_role('admin').to_sql
#=> "SELECT \"users\".* FROM \"users\"  WHERE ((\"roles_mask\" & 1) > 0)"

IMOは、より概念実証であると言わなければなりません。再利用する予定がなくbitwise_andbitmask他のモデルではそれらを抽象化する必要がない場合は、おそらくscope、このようなfe何か:

class User < ActiveRecord::Base
  ROLES = %w[admin moderator author]

  BITMASK_SQL =
    case connection.adapter_name
    when 'Oracle' # probably wrong!
      "BITAND(%s, %s) > 0"
    else
      "%s & %s > 0"
    end

  scope :with_role, ->(role) do
    where BITMASK_SQL % [ connection.quote_column_name(:roles_mask), 
                          connection.quote(2**ROLES.index(role.to_s)) ]
  end
end

p User.with_role('admin').to_sql
#=> "SELECT \"users\".* FROM \"users\"  WHERE (\"roles_mask\" & 1 > 0)"

ルールは、必要なときに抽象化を追加することだと思いますが、必要のないときは追加しないでください(このフレーズの英語が正しいかどうかはわかりません:-))

別のことを言いたいのですが、あなたはRuby / Railsに慣れていないので、Rails&cをたくさん読むことをお勧めします。コード; IMOは、Railsがどのように機能するかを学ぶための最良の方法です(これが、私があなたの質問に答えるために時間を費やした理由です:私はArel関係のRails管理に興味があったからです:-))。

于 2013-03-09T17:57:50.390 に答える