ActiveRecordまとめ3

あとは残ったメソッドをちょこちょこみよう。

ActiveRecord::Base#first
      def first(*args)
        find(:first, *args)
      end

find呼び出しているだけ。

ActiveRecord::Base#last
      def last(*args)
        find(:last, *args)
      end

firstと同じ。

ActiveRecord::Base#all
      def all(*args)
        find(:all, *args)
      end

これも同じ。

ActiveRecord::Base#exists?

呼び出し方

find.exists?(1)
find.exists?(:name=>"David")
find.exists?(["name LIKE ?"], "%#{query}%")


コードはこんなの。

      def exists?(id_or_conditions = {})
        find_initial(
          :select => "#{quoted_table_name}.#{primary_key}",
          :conditions => expand_id_conditions(id_or_conditions)) ? true : false
      end

find_initial()で取得してるだけか。
お、知らないメソッド(expand_id_conditions)を見つけたので、調べよう。

ActiveRecord::Base#expand_id_conditions
        def expand_id_conditions(id_or_conditions)
          case id_or_conditions
            when Array, Hash then id_or_conditions
            else sanitize_sql(primary_key => id_or_conditions)
          end
        end
  • Array/Hashの時はそのまま返す。
  • それ以外の時は、primary_keyとして返すんだな。だな。
ActiveRecord::Base#update

呼び出し方はこうか。フムフム。

 Person.update(15, :user_name => 'Samuel', :group => 'expert')

 people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } }
 Person.update(people.keys, people.values)

コードはどんなことしてんのかな。

      def update(id, attributes)
        if id.is_a?(Array)
          idx = -1
          id.collect { |one_id| idx += 1; update(one_id, attributes[idx]) }
        else
          object = find(id)
          object.update_attributes(attributes)
          object
        end
      end
  • idがArrayだったら複数行のupdateと判断して、複数行のupdateをしている。(自分自身を呼び出している)
  • idがArrayじゃなければ、idで検索して、attributesを設定してupdateな。

引数のidの名前は、id_or_array_of_idとかだったらわかりやすい気がした。

そういえば、update_attributes()は何してるんだ?

ActiveRecord::Base#update_attributes
      def update_attributes(attributes)
        self.attributes = attributes
        save
      end

自分のattributesを入れて、saveしているだけか。
save内では、SQL Injectionがおこせないので問題ないなー。

ActiveRecord::Base#delete

おお、しまった。delete()を忘れていた。

      def delete(id)
        delete_all([ "#{connection.quote_column_name(primary_key)} IN (?)", id ])
      end

delete_allでprimary_keyの値を渡しているのかな。

ActiveRecord::Base#delete_all
      def delete_all(conditions = nil)
        sql = "DELETE FROM #{quoted_table_name} "
        add_conditions!(sql, conditions, scope(:find))
        connection.delete(sql, "#{name} Delete all")
      end

add_conditions!はfindの時にでてきたな。
conditionsがArrayとかHashの場合に、sanitizeした WHERE句を作って
作ったクエリをconnection.delete() に渡す。

ActiveRecord::Base#destroy

クラスメソッドの方のdestroy
ActiveRecord::Base#destroyの#と何かで、クラスメソッドと、インスタンスメソッドの区別ができたような気がするんだけど、書き方知っている人いたら教えてください。*1

      def destroy(id)
        if id.is_a?(Array)
          id.map { |one_id| destroy(one_id) }
        else
          find(id).destroy
        end
      end

destroyはidか、idのArrayしか受け取らない。

  • Arrayだったら、繰り返して自分自身を呼ぶっぽいなー。
  • Arrayじゃなければ、idでfindしたオブジェクトに対して、destoryを呼んでいる。

インスタンスのdestoryはどんな実装になってんだ。

      def destroy
        unless new_record?
          connection.delete(
            "DELETE FROM #{self.class.quoted_table_name} " +
            "WHERE #{connection.quote_column_name(self.class.primary_key)} = #{quoted_id}",
            "#{self.class.name} Destroy"
          )
        end

        @destroyed = true
        freeze
      end
  • primary_keyとidを使って、deleteを呼んでいる、と。


SQL Injection出来そうなところはこれだけっぽい。

結論として、正しい使い方*2をしている限り、Injectionはできないっぽい。
エンコーディングが違う文字列を受け取るとまた違うのかな?

*1:Rubyのリファレンスを探してもそれらしい例題が見つからなかった。

*2:find(:all, :conditions=>["xxx = #{param[:recieved_string_from_user]}"])とかはダメな例