• Rails Gossip: ActiveRecord 基礎


    ActiveRecord是Rails的物件/關聯映射(Object/Relational Mapping, ORM)解決方案,可以物件角度來進行資料庫操作,將物件與物件關係,映射至關聯資料庫表格與表格的關係,Rails的ActiveRecord方案實現了ActiveRecord 模式,物件本身代表資料表格中的一筆資料,物件本身攜帶資料庫存取的相關操作方法。

    先前在 MVC 與 Rails 中曾略為看過ActiveRecord的一些使用,當你使用rails generate model產生指定的資料庫模型物件時,會產生幾個檔案:

    $ rails g model message name:string content:text is_read:boolean
          invoke  active_record
          create    db/migrate/20111214072231_create_messages.rb
          create    app/models/message.rb
          invoke    test_unit
          create      test/unit/message_test.rb
          create      test/fixtures/messages.yml


    以上例而言,*_create_messages.rb記錄了稍後執行db:migrate時要作的動作,其中*是時間標記,用以避免名稱衝突,就之前的指令主要是建立新表格:

    • *_create_messages.rb
    class CreateMessages < ActiveRecord::Migration
      def change
        create_table :messages do |t|
          t.string :name
          t.text :content
          t.boolean :is_read
    
          t.timestamps
        end
      end
    end

    create_table方法指定建立messages表格,在程式區塊中分別指定t.string、t.text、t.boolean等建立了name、content、is_read欄位,t.timestamps則會自動建立created_at與updated_at兩個欄位,每個表格也會有個自動增生的id欄位作為主鍵。為了執行*_create_messages.rb的內容以建立表格,需要執行db:migrate任務:

    ~/hello$ bundle exec rake db:migrate

    使用的SQL可在log/development.log中查到:

    CREATE TABLE "messages" (
        "id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, 
        "name" varchar(255), 
        "content" text, 
        "is_read" boolean, 
        "created_at" datetime, 
        "updated_at" datetime

    message.rb中定義了對應資料庫表格的模型:

    • message.rb
    class Message < ActiveRecord::Base
    end


    ActiveRecord是Rails中提供ORM(Object-Relaction Mapping)的元件,預設的message.rb只繼承ActiveRecord:Base定義的基本功能,基本上,類別方法是對整體資料庫的操作,實例方法是對表格中各筆資料的操作。

    ActiveRecord物件並不支援在類別上定義屬性,而是直接參考所連結的資料表格,要新增、刪除或改變屬性,都是直接對資料庫表格相關欄位加以修改,資料表格欄位的變化,會反應在ActiveRecord的屬性

    要準備一筆新的資料,可以使用new方法,例如:

    message = Message.new
    message.name = "Justin"
    message.content = "Justin's message"

    欄位名稱對應會實例方法名稱,另一個建立實例並指定資料的方式是:

    message = Message.new { |msg|
        msg.name = "Justin"
        msg.content = "Justin's message"
    }

    最簡單建立實例並準備資料的方式,是在new時給予一個Hash實例,這對於接受params請求參數時相當方便

    message = Message.new(:name => "Justin", :content => "Hi! Justin!")

    此時建立的實例,只是記憶體中的一筆資料,還沒有真正新增至資料庫,如果要儲存資料,可以使用save或save!方法。例如:

    message.save

    save儲存成功與否,會傳回布林值true或false表示,save!如果成功會傳回true,如果失敗會丟出例外,如果是新建物件執行save或save!,會執行INSERT。例如:

    INSERT INTO "messages" ("content", "created_at", "is_read", "name", "updated_at") VALUES (?, ?, ?, ?, ?)

    如果緊接著修改了物件的屬性,再次執行儲存,則會進行UPDATE。例如若僅message.content = "XD",再次執行save或save!會如下:

    UPDATE "messages" SET "content" = 'XD', "updated_at" = '2012-01-13 03:43:38.294211' WHERE "messages"."id" = 1

    如果想直接建立實例並儲存至資料庫,可以使用create或create!。例如:

    Message.create(:name => "Justin", :content => "Hi! Justin!")

    物件建立並儲存為資料表中的一筆資料後,物件的id就會被指定,此時物件就對應於資料庫的一筆資料,可以用new_record?來得知資料是否已經在資料庫。如果要查找資料,可以使用find指定id查找。例如:

    Message.find(1)

    如果有兩筆以上要查找的資料,可以使用find指定多個id,此時會以陣列方式傳回實例。例如:

    Message.find(1, 2) # 或Message.find([1, 2])

    直接更新物件上的屬性,並不會直接對資料庫中的資料造成修改,而必須在執行save或save!之後,才會對資料庫造成更新。除了根據id查找的find方法之外,可以使用find_by_*或find_all_by_*方法,前者只找回第一個符合資料,後者則找回所有符合資料,*部份表示欄位名稱,可以使用and連接。例如:

    Message.find_by_name("Justin")
    Message.find_all_by_name("Justin")
    Message.find_by_name_and_content("Justin", "Hi, Justin!")

    all可以取得所有資料,first可以取得第一筆資料,last可以取得最後一筆資料,也可以使用where指定條件。例如:

    Message.where(:name => "Justin", :is_read => nil) # 結果相當於 Message.find_all_by_name_and_is_read("Justin", nil)
    Message.where("name = ? or is_read = ?", "Justin", nil)

    可以注意一開始就使用find_all_by、all等方法與where的不同,find_all_by、all等方法會一次取出所有符合資料至記憶體,如果之後有緊接著其它限定方法,則是從記憶體中符合的資料中再取得,where之後若緊接著有一些限定方法,將會影響產生的SQL。例如Message.find_all_by_name("Justin").last與Message.where(:name => "Justin").last,最後取得的物件雖然相同的,但自動產生的SQL卻是不同。例如Message.find_all_by_name("Justin").last會產生以下的SQL:

    SELECT "messages".* FROM "messages" WHERE "messages"."name" = 'Justin'

    Message.where(:name => "Justin").last則會產生以下的SQL:

    SELECT "messages".* FROM "messages" WHERE "messages"."name" = 'Justin' ORDER BY "messages"."id" DESC LIMIT 1

    以上兩個例子,在資料筆數多時,使用where會比較有效率,像是搭配count、sum、maximum、average等統計方法:

    Message.where(:name => "Justin").count

    以上會比Message.find_all_by_name("Justin").count來得有效率。

    可以使用limit限制查詢筆數。例如:

    messages = Message.limit(5).find_all_by_name("Justin")

    可以使用offset設定從第幾筆查到的資料之後開始取得,通常用於分頁查詢:

    messages = Message.offset(2).find_by_name("Justin")

    可以使用order作排序,用reorder取消排序。例如:

    Message.order("name").find_by_name("Justin")
    Message.order("name DESC").find_by_name("Justin")
    Message.order("name DESC, updated_at ASC").find_by_name("Justin")

    可以使用select指定只取出哪些欄位。例如:

    Message.select("content").find_all_by_name("Justin")
    Message.select("content, is_read").find_all_by_name("Justin")

    以上介紹到的where、limit、offset、select、order等方法可以自由組合,以取得想要的查詢,方法呼叫組合的結果會影響產生的SQL語句,不確定哪種組合有效率的話,可以在Rails console中試試,觀察產生的SQL。如果真要自己下SQL查找,可以使用find_by_sql。例如:

    messages = Message.find_by_sql("SELECT * FROM messages")

    如果查詢出來的資料不希望被修改,可以使用readonly。例如:

    message = Message.readonly.find(1)

    如上取得的資料,如果message的屬性被更改而後嘗試更新資料庫,或者嘗試刪除資料,就會引發例外。

    如果要更新資料,除了對查找到的資料使用save或save!之外,還可以使用update_attributes或update_attributes!,單一屬性更新則使用update_attribute或update_attribute!。例如:

    message.update_attributes(:content => "Justin! Welcome to Rails!", :is_read => true)
    message.update_attribute("name", "caterpillar")

    如果資料庫中某筆資料有變更,物件可以使用reload來重新載入資料。例如:

    message.reload

    如果已取得資料,可以使用destroy刪除資料。例如:

    message.destroy

    或者也可以透過類別方法delete、delete_all、destroy_all來刪除資料。例如:

    Message.delete(1) # 刪除id為1的資料
    Message.delete_all(["updated_at < ?", 10.minute.ago]

    以上先介紹基本的增刪查找,物件與資料表關聯在之後會介紹,更多資料則可參考 ActiveRecord::BASE 文件,或是 Active Record Query Interface 文件,交易(Transaction)則可參考 Active Record Transactions

  • 相关阅读:
    使用uibesizerpath + Cashaplayer画椭圆
    国庆节,回乡
    慎用单例
    终于碰到iOS对象集合深拷贝的坑
    Oracle 按表名导出数据
    代理模式(Proxy Pattern)
    享元模式(Flyweight Pattern)
    外观模式(Facade Pattern)
    组合模式
    装饰者模式
  • 原文地址:https://www.cnblogs.com/rywx/p/2703745.html
Copyright © 2020-2023  润新知