Ruby on Rails
ActiveRecord Query Interface
Sök…
Introduktion
ActiveRecord är M i MVC som är det lager i systemet som ansvarar för att representera affärsdata och logik. Tekniken som kopplar en applikations rika objekt till tabeller i ett relationsdatabashanteringssystem är O bject R elational M apper ( ORM ).
ActiveRecord kommer att utföra frågor i databasen för dig och är kompatibel med de flesta databassystem. Oavsett vilket databassystem du använder kommer ActiveRecord-metodformatet alltid att vara detsamma.
.var
where
metoden finns tillgänglig på alla ActiveRecord
modeller och gör det möjligt att fråga databasen efter en uppsättning poster som matchar de givna kriterierna.
where
metoden accepterar en hash där tangenterna motsvarar kolumnnamnen i tabellen som modellen representerar.
Som ett enkelt exempel kommer vi att använda följande modell:
class Person < ActiveRecord::Base
#attribute :first_name, :string
#attribute :last_name, :string
end
För att hitta alla personer med förnamnet Sven
:
people = Person.where(first_name: 'Sven')
people.to_sql # "SELECT * FROM people WHERE first_name='Sven'"
För att hitta alla personer med förnamnet Sven
och efternamnet på Schrodinger
:
people = Person.where(first_name: 'Sven', last_name: 'Schrodinger')
people.to_sql # "SELECT * FROM people WHERE first_name='Sven' AND last_name='Schrodinger'"
I exemplet ovan visar sql-utdata att poster endast kommer att returneras om både first_name
och last_name
matchar.
fråga med ELLER villkor
Att hitta poster med first_name == 'Bruce'
ELLER last_name == 'Wayne'
User.where('first_name = ? or last_name = ?', 'Bruce', 'Wayne')
# SELECT "users".* FROM "users" WHERE (first_name = 'Bruce' or last_name = 'Wayne')
.var med en matris
where
metoden på vilken ActiveRecord-modell som helst kan användas för att generera SQL med formen WHERE column_name IN (a, b, c, ...)
. Detta uppnås genom att skicka en grupp som argument.
Som ett enkelt exempel kommer vi att använda följande modell:
class Person < ActiveRecord::Base
#attribute :first_name, :string
#attribute :last_name, :string
end
people = Person.where(first_name: ['Mark', 'Mary'])
people.to_sql # "SELECT * FROM people WHERE first_name IN ('Mark', 'Mary')"
Om matrisen innehåller en nil
kommer SQL att ändras för att kontrollera om kolumnen är null
:
people = Person.where(first_name: ['Mark', 'Mary', nil])
people.to_sql # "SELECT * FROM people WHERE first_name IN ('Mark', 'Mary') OR first_name IS NULL"
Scopes
Scopes fungerar som fördefinierade filter på ActiveRecord
modeller.
Ett omfattningsområde definieras med metod för scope
.
Som ett enkelt exempel kommer vi att använda följande modell:
class Person < ActiveRecord::Base
#attribute :first_name, :string
#attribute :last_name, :string
#attribute :age, :integer
# define a scope to get all people under 17
scope :minors, -> { where(age: 0..17) }
# define a scope to search a person by last name
scope :with_last_name, ->(name) { where(last_name: name) }
end
Omfång kan kallas direkt från modellklassen:
minors = Person.minors
Räckvidd kan kedjas:
peters_children = Person.minors.with_last_name('Peters')
where
metoden och andra frågestypmetoder kan också kedjas:
mary_smith = Person.with_last_name('Smith').where(first_name: 'Mary')
Bakom kulisserna är scopes helt enkelt syntaktiskt socker för en standardklassmetod. Till exempel är dessa metoder funktionellt identiska:
scope :with_last_name, ->(name) { where(name: name) }
# This ^ is the same as this:
def self.with_last_name(name)
where(name: name)
end
Standardområde
i din modell för att ange ett standardomfång för alla operationer på modellen.
Det finns en anmärkningsvärd skillnad mellan
scope
och en klassmetod:scope
kommer alltid att returnera ettActiveRecord::Relation
, även om logiken inom returnerar noll. Klassmetoder har emellertid inget sådant säkerhetsnät och kan bryta kedjan om de returnerar något annat.
where.not
where
klausuler kan negeras med hjälp av where.not
class Person < ApplicationRecord #attribute :first_name, :string end people = Person.where.not(first_name: ['Mark', 'Mary']) # => SELECT "people".* FROM "people" WHERE "people"."first_name" NOT IN ('Mark', 'Mary')
Stöds av ActiveRecord 4.0 och senare.
Beställning
Du kan beställa ActiveRecord- frågeställningar med .order
:
User.order(:created_at)
#=> => [#<User id: 2, created_at: "2015-08-12 21:36:23">, #<User id: 11, created_at: "2015-08-15 10:21:48">]
Om det inte anges kommer beställningen att utföras i stigande ordning. Du kan ange det genom att göra:
User.order(created_at: :asc)
#=> => [#<User id: 2, created_at: "2015-08-12 21:36:23">, #<User id: 11, created_at: "2015-08-15 10:21:48">]
User.order(created_at: :desc)
#=> [#<User id: 7585, created_at: "2016-07-13 17:15:27">, #<User id: 7583, created_at: "2016-07-13 16:51:18">]
.order
accepterar också en sträng, så du kan också göra det
User.order("created_at DESC")
#=> [#<User id: 7585, created_at: "2016-07-13 17:15:27">, #<User id: 7583, created_at: "2016-07-13 16:51:18">]
Eftersom strängen är rå SQL kan du också ange en tabell och inte bara ett attribut. Förutsatt att du vill beställa users
enligt deras role
kan du göra detta:
Class User < ActiveRecord::Base
belongs_to :role
end
Class Role < ActiveRecord::Base
has_many :users
end
User.includes(:role).order("roles.name ASC")
order
kan också acceptera en Arel-nod:
User.includes(:role).order(User.arel_table[:name].asc)
ActiveRecord Bang (!) -Metoder
Om du behöver en ActiveRecord- metod för att höja ett undantag istället för ett false
värde vid fel kan du lägga till !
till dem. Det här är väldigt viktigt. Som vissa undantag / misslyckanden är svåra att fånga om du inte använder det! på dem. Jag rekommenderade att du gör detta i din utvecklingscykel för att skriva all din ActiveRecord-kod på detta sätt för att spara tid och problem.
Class User < ActiveRecord::Base
validates :last_name, presence: true
end
User.create!(first_name: "John")
#=> ActiveRecord::RecordInvalid: Validation failed: Last name can't be blank
ActiveRecord- metoderna som accepterar ett slag ( !
) Är :
-
.create!
-
.take!
-
.first!
-
.last!
-
.find_by!
-
.find_or_create_by!
-
#save!
-
#update!
- alla dynamiska AR-sökare
.find_by
Du kan hitta poster efter valfritt fält i tabellen med find_by
.
Så om du har en User
modell med first_name
attribut du kan göra:
User.find_by(first_name: "John")
#=> #<User id: 2005, first_name: "John", last_name: "Smith">
Tänk på att find_by
inte kastar något undantag som standard. Om resultatet är en tom uppsättning, returnerar det nil
istället för att find
.
Om undantaget behövs kan du find_by!
som höjer ett ActiveRecord::RecordNotFound
fel som find
.
.radera allt
Om du behöver ta bort en hel del skivor snabbt ger Active .delete_all
metod. att kallas direkt på en modell, för att radera alla poster i den tabellen eller en samling. Akta dig dock, eftersom .delete_all
inte ger något objekt och därför inte ger någon återuppringning ( before_*
och after_destroy
inte after_destroy
).
User.delete_all
#=> 39 <-- .delete_all return the number of rows deleted
User.where(name: "John").delete_all
ActiveRecord-okänslig sökning
Om du behöver söka i en ActiveRecord-modell efter liknande värden kanske du frestas att använda LIKE
eller ILIKE
men detta är inte portabelt mellan databasmotorer. På liknande sätt kan man skapa prestandafrågor att ta tillvara alltid att stänga ned eller stiga.
Du kan använda ActiveRecords underliggande Arel- matches
för att göra detta på ett säkert sätt:
addresses = Address.arel_table
Address.where(addresses[:address].matches("%street%"))
Arel kommer att använda lämpliga LIKE- eller ILIKE-konstruktioner för den konfigurerade databasmotorn.
Få första och sista posten
Rails har mycket enkelt sätt att få first
och last
posten från databasen.
För att få den first
posten från users
måste vi skriva följande kommando:
User.first
Det genererar följande sql
fråga:
SELECT `users`.* FROM `users` ORDER BY `users`.`id` ASC LIMIT 1
Och kommer tillbaka efter posten:
#<User:0x007f8a6db09920 id: 1, first_name: foo, created_at: Thu, 16 Jun 2016 21:43:03 UTC +00:00, updated_at: Thu, 16 Jun 2016 21:43:03 UTC +00:00 >
För att få den last
posten från users
måste vi skriva följande kommando:
User.last
Det genererar följande sql
fråga:
SELECT `users`.* FROM `users` ORDER BY `users`.`id` DESC LIMIT 1
Och kommer tillbaka efter posten:
#<User:0x007f8a6db09920 id: 10, first_name: bar, created_at: Thu, 16 Jun 2016 21:43:03 UTC +00:00, updated_at: Thu, 16 Jun 2016 21:43:03 UTC +00:00 >
Att skicka ett heltal till den första och sista metoden skapar en LIMIT- fråga och returnerar en rad objekt.
User.first(5)
Det kommer att generera följande sql
fråga.
SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 5
Och
User.last(5)
Det kommer att generera följande sql
fråga.
SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT 5
.grupp och .antal
Vi har en Product
och vi vill gruppera dem efter deras category
.
Product.select(:category).group(:category)
Detta frågar databasen på följande sätt:
SELECT "product"."category" FROM "product" GROUP BY "product"."category"
Se till att det grupperade fältet också är valt. Gruppering är särskilt användbar för att räkna förekomsten - i det här fallet - av categories
.
Product.select(:category).group(:category).count
Som frågan visar kommer den att använda databasen för att räkna, vilket är mycket effektivare än att hämta all post först och göra räkningen i koden:
SELECT COUNT("products"."category") AS count_categories, "products"."category" AS products_category FROM "products" GROUP BY "products"."category"
.distinkt (eller .uniq)
Om du vill ta bort dubbletter från ett resultat kan du använda .distinct()
:
Customers.select(:country).distinct
Detta frågar databasen på följande sätt:
SELECT DISTINCT "customers"."country" FROM "customers"
.uniq()
har samma effekt. Med Rails 5.0 försvann den och den kommer att tas bort från Rails med version 5.1. Anledningen är att ordet unique
inte har samma betydelse som distinkt och det kan vara vilseledande. Dessutom distinct
är närmare SQL-syntaxen.
Fogar
joins()
låter dig koppla tabeller till din nuvarande modell. För ex.
User.joins(:posts)
kommer att producera följande SQL-fråga:
"SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id""
När du har anslutit tabellen har du tillgång till det:
User.joins(:posts).where(posts: { title: "Hello world" })
Var uppmärksam på plural form. Om din relation är :has_many
, bör argumentet för joins()
vara pluraliserat. Använd annars singular.
Nested joins
:
User.joins(posts: :images).where(images: { caption: 'First post' })
som kommer att producera:
"SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id" INNER JOIN "images" ON "images"."post_id" = "images"."id""
inkluderar
ActiveRecord med includes
säkerställer att alla angivna föreningar laddas med minsta möjliga antal frågor. Så när du frågar en tabell för data med en tillhörande tabell, laddas båda tabellerna i minnet.
@authors = Author.includes(:books).where(books: { bestseller: true } )
# this will print results without additional db hitting
@authors.each do |author|
author.books.each do |book|
puts book.title
end
end
Author.joins(:books).where(books: { bestseller: true } )
laddar bara författare med villkor i minnet utan att ladda böcker . Använd joins
när ytterligare information om kapslade föreningar inte krävs.
@authors = Author.joins(:books).where(books: { bestseller: true } )
# this will print results without additional queries
@authors.each { |author| puts author.name }
# this will print results with additional db queries
@authors.each do |author|
author.books.each do |book|
puts book.title
end
end
Begränsning och förskjutning
Du kan använda limit
att berätta antalet poster som ska hämtas och använda offset
att berätta antalet poster som ska hoppas över innan du börjar returnera posterna.
Till exempel
User.limit(3) #returns first three records
Det kommer att generera följande sql-fråga.
"SELECT `users`.* FROM `users` LIMIT 3"
Eftersom offset inte nämns i ovanstående fråga så kommer det att returnera de tre första posterna.
User.limit(5).offset(30) #returns 5 records starting from 31th i.e from 31 to 35
Det kommer att generera följande sql-fråga.
"SELECT `users`.* FROM `users` LIMIT 5 OFFSET 30"