<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                # 4.2 深入模型查詢 ## 概要: 本課時講解模型在數據查詢時,如何避免 N+1問題,使用 scope 包裝查詢條件,編寫模型 Rspec 測試。 ## 知識點: 1. N+1 1. Scope 1. 實用的查詢 1. Rspec 測試 ## 正文 ### 4.2.1 兩個 Gem ActiveRecord 這個 gem 中,包含了兩個重要的 gem,打開它的 [源代碼](https://github.com/rails/rails/blob/master/activerecord/activerecord.gemspec),可以看到這兩個 gem:[activemodel](https://github.com/rails/rails/tree/master/activemodel) 和 [arel](https://github.com/rails/arel)。 `activemodel` 為一個類增加了許多特性,比如屬性校驗,回調等,這在后面章節會介紹。 `arel` 是 Ruby 編寫的 sql 工具,使用它,可以通過簡單的 Ruby 語法,編寫復雜 sql 查詢,我們上面使用的例子,語法就來自 arel。arel 還可以面向多種關系型數據庫。 ActiveRecord 在使用 arel 的時候,提供了一個方法:sanitize_sql。 在我們以上的講解中,會經常傳遞這樣的參數 `["name = ? and price=?", "foobar", 4]`,它會由 `sanitize_sql` 方法進行處理,這是一個 protected 方法,我們使用 send 來調用它: ~~~ Product.send(:sanitize_sql, ["name = ? and price=?", "Shoes", 4]) => "name = 'Shoes' and price=4" ~~~ 這是一種安全的手段,保護我們的 sql 不會被插入惡意代碼。我們不必去直接使用這個方法,除非特殊情況,我們只需要按照它的格式要求來書寫就可以了。 ### 4.2.2 N+1 N+1 是查詢中經常遇到的一個問題。在下一節里,我們經常使用關聯關系的查詢,比如,列出十個用戶的同時,顯示它地址中的電話: ~~~ users = User.limit(10) users.each do |user| puts user.address.phone end ~~~ 這樣就會造成,在 each 中又去查詢數據,得到電話。這種情況會經常出現在我的列表中,所以在列表中會經常遇到 N+1 的問題。 為了避免這個問題,Rails 提供了預加載的功能,在查詢的時候,使用 `includes` 來解決。上面的例子修改一下: ~~~ users = User.includes(:address).limit(10) users.each do |user| puts user.address.phone end ~~~ 我們查看一下終端的輸出: ~~~ SELECT * FROM users LIMIT 10 SELECT addresses.* FROM addresses WHERE (addresses.user_id IN (1,2,3,4,5,6,7,8,9,10)) ~~~ 這里只有兩個 sql 查詢,提高了查詢效率。 ### 4.2.3 查詢中使用 Scope 當我們使用 where 查詢的時候,會遇到多個條件組合查詢。通常我們可以把它們都寫到一個 where 的條件里,比如: ~~~ Product.where(name: "T-Shirt", hot: true, top: true) ~~~ 我增加了兩個條件,`hot: true` 和 `top: true`,但是,這種條件組合只能在這里使用,在其他地方,我們還要再寫一遍,這不符合 Rails 的哲學:“不要重復自己”。 Rails 提供了 scope,讓我們復用查詢條件: ~~~ class Product < ActiveRecord::Base scope :hot, -> { where(hot: true) } scope :top, -> { where(top: true) } end ~~~ 使用的時候,我們可以將多個 scope 組合在一起: ~~~ Product.top.hot.where(name: "T-Shirt") ~~~ `default_scope` 可以為所有查詢加上它定義的查詢條件,比如: ~~~ class Product < ActiveRecord::Base default_scope { where("deleted_at IS NULL") } end ~~~ `default_scope` 要慎用,慎用,慎用(重要的話說三遍),在我們程序變的復雜的時候,性能往往會消耗在數據庫查詢上,維護已有查詢時,很容易忽視 default_scope 的作用。如果使用了 default_scope,而在其他地方不得不去掉它,可以使用 unscoped,然后再附上其他查詢: ~~~ Product.unscoped.load.top.hot ~~~ 如果一個地方使用了某個 scope,而要在另一個地方把它的條件改變,可以使用 merge: ~~~ class Product < ActiveRecord::Base scope :active, -> { where state: 'active' } scope :inactive, -> { where state: 'inactive' } end ~~~ 看一下它的執行結果: ~~~ Product.active.merge(User.inactive) # SELECT "products".* FROM "products" WHERE "products"."state" = 'inactive' ~~~ ### 4.2.4 實用的查詢 ### 4.2.4.1 sql 查詢集合 我們使用where查詢,得到的是 ActiveRecord::Relation 實例,它的源代碼在[這里](https://github.com/rails/rails/blob/master/activerecord/lib/active_record/relation.rb)。閱讀這里的代碼,會讓你學習到更多優雅的查詢方法。在查詢時,我們還可以使用 sql 直接查詢,如果你更熟悉 sql 語法,可以這樣來查詢: ~~~ Client.find_by_sql("SELECT * FROM clients INNER JOIN orders ON clients.id = orders.client_id ORDER BY clients.created_at desc") # => [ #<Client id: 1, first_name: "Lucas" >, #<Client id: 2, first_name: "Jan" >, # ... ] ~~~ 這個例子來自[這里](http://guides.rubyonrails.org/active_record_querying.html#dynamic-finders)。 它返回的是實例的集合,這在我們 Rails 內使用很方便,但是提供 json 格式的 api時,需要轉換一下,不過我們可以用 select_all 查詢,得到包含 hash 的 array: ~~~ Client.connection.select_all("SELECT first_name, created_at FROM clients WHERE id = '1'") # => [ {"first_name"=>"Rafael", "created_at"=>"2012-11-10 23:23:45.281189"}, {"first_name"=>"Eileen", "created_at"=>"2013-12-09 11:22:35.221282"} ] ~~~ ### 4.2.4.2 pluck pluck 可以直接在 Relation 實例的基礎上,使用 sql 的 select 方法,得到字段值的集合(Array),而不用把返回結果包裝成 ActiveRecord 實例,再得到屬性值。在查詢屬性集合時,`pluck` 的性能更高。 ~~~ Client.where(active: true).pluck(:id) SELECT id FROM clients WHERE active = 1 => [1, 2, 3] Client.distinct.pluck(:role) SELECT DISTINCT role FROM clients => ['admin', 'member', 'guest'] Client.pluck(:id, :name) SELECT clients.id, clients.name FROM clients => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']] ~~~ ActiveRecord 有一個類似的方法,select,比較下兩者的區別: ~~~ Product.select(:id, :name) Product Load (8.5ms) SELECT "products"."id", "products"."name" FROM "products" => #<ActiveRecord::Relation [#<Product id: 1, name: "f">]> Product.pluck(:id, :name) (0.3ms) SELECT "products"."id", "products"."name" FROM "products" => [[1, "f"]] ~~~ 前者顯示返回 AR 實例,然后取其屬性值,后者直接讀取數據庫記錄,返回數組。 pluck 只能用在查詢的最后,因為它直接返回了結果,而不是 ActiveRecord::Relation。 ### 4.2.4.3 ids ids 返回主鍵集合: ~~~ Person.ids => SELECT id FROM people ~~~ 不要被 ids 字面迷惑,它返回的是主鍵的集合,我們可以在 model 里設定其他字段為主鍵。 ~~~ class Person < ActiveRecord::Base self.primary_key = "person_id" end Person.ids => SELECT person_id FROM people ~~~ ### 4.2.4.4 查詢記錄數量 這里有四個方法,方便我們判斷一個模型中的記錄數量。 ~~~ Client.exists?(1) Client.exists?(id: [1,2,3]) Client.exists?(name: ['John', 'Sergei']) ~~~ `exists?` 判斷記錄是否存在,和它類似的方法有兩個: ~~~ Client.exists? [1] Client.any? [2] Client.many? [3] ~~~ [1] 是否有記錄[2] 是否至少有一條記錄[3] 是否有多于一條的記錄 any? 和 many? 與 exists? 不同的是,他們可以使用在 Relation 實例上,比如: ~~~ Article.where(published: true).any? Article.where(published: true).many? ~~~ 還可以接收 block: ~~~ person.pets.any? do |pet| pet.group == 'cats' end => false person.pets.many? do |pet| pet.group == 'dogs' end => true ~~~ ### 4.2.4.5 查詢記錄數量 下面五個方法,完全可以按照字面意義理解,并且適用于 Relation 上: ~~~ Client.count Client.average("orders_count") Client.minimum("age") Client.maximum("age") Client.sum("orders_count") ~~~ 以上的例子來自 [這里](http://guides.rubyonrails.org/active_record_querying.html),閑暇的時候應該多讀讀這個文檔,翻看源碼。 ### 4.2.5 Rspec 測試 在深入 Rails 項目開發之后,測試環節是一個重要的環節。Ruby 為我們提供了非常方便的測試框架,Rails 也可以方便的執行這些測試框架。 在 Rails 3.x 及之前的版本里,默認使用 [TestUnit](https://github.com/test-unit/test-unit) 框架,4.x 之后改為 [MiniTest](https://github.com/seattlerb/minitest) 框架。我們可以查看 [test_case.rb](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/test_case.rb) 文件,看到其中的變化。 除了這兩個測試框架,[Rspec](https://github.com/rspec/rspec) 也是經常用到的 Ruby 測試框架。 我們在 Rails 里安裝 rpesc,和其他的幾個 gem: ~~~ group :development, :test do gem 'rspec-rails' gem "factory_girl_rails" gem "database_cleaner" end ~~~ [rspec-rails](https://github.com/rspec/rspec-rails) 是 [rspec](http://rspec.info/) 的 Rails 集成,在 Rails 中初始化 rspec 的命令是: ~~~ rails generate rspec:install ~~~ 它會創建兩個文件,和 spec 文件件。運行 rpsec 測試的命令非常簡單,`rspec` 就可以,他會自動運行 spec 文件夾下所有的 xxx_spec.rb 文件,也可以指定某個文件: ~~~ rspec spec/models/product_spec.rb ~~~ 也可以只運行某一個測試用例,這需要指定該用例開始的行數: ~~~ rspec spec/models/product_spec.rb:10 ~~~ 也可以運行某一個目錄: ~~~ rspec spec/models/ ~~~ [factory_girl_rails](https://github.com/thoughtbot/factory_girl_rails) 是 [factory_girl](https://github.com/thoughtbot/factory_girl) 的 Rails 包裝。factory_girl 可以為我們的測試代碼提供模擬的測試數據。 [database_cleaner](https://github.com/DatabaseCleaner/database_cleaner) 可以在每一次運行測試的時候,清空測試數據庫。我們在 config/database.yml 中,會設置三種運行環境,test 環境要單獨設置數據庫,也就是因為測試時會反復填入和刪除數據。一般,test 使用的是 sqlite 數據庫,而 production 使用 mysql、postgresql 等數據庫。 我們需要配置下 spec 的運行環境: ~~~ RSpec.configure do |config| config.before(:each) do DatabaseCleaner.strategy = :truncation DatabaseCleaner.clean end end ~~~ #### 4.2.5.1 Model 測試 在使用 generator 創建 model 文件的時候,rspec 會自動創建它對應的 spec 文件。我們打開 product_spec.rb 文件: ~~~ require 'rails_helper' RSpec.describe Product, type: :model do pending "add some examples to (or delete) #{__FILE__}" end ~~~ 我們為它增加一個測試: ~~~ RSpec.describe Product, type: :model do it "should create a product" do tshirt = Product.create(name: "T-Shirt", price: 9.99) expect(tshirt.name).to eq("T-Shirt") expect(tshirt.price).to eq(9.99) end end ~~~ 運行一下這個測試: ~~~ rspec spec/models/product_spec.rb . Finished in 0.081 seconds (files took 2.37 seconds to load) 1 example, 0 failures ~~~ 這個測試的目的,是確保 create 方法可以為我們創建一個 product 實例。更多 rspec 語法可以查看 rspec 文檔,或者 [《使用 RSpec 測試 Rails 程序》](https://selfstore.io/products/3)一書。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看