在 Ruby on Rails 框架中使用 PostgreSQL 特定資料類型

閱讀時間約 22 分鐘

Ruby on Rails 是一個使用 Ruby 語言編寫的開源 Web 應用程式框架。

PostgreSQL 是一個強大、開源的物件關聯式資料庫系統,擁有超過 35 年的活躍開發歷程,並以其可靠性、功能強大性和效能而享有盛譽。

PostgreSQL 提供許多特定資料類型,以下是 Rails 支援的 PostgreSQL 類型清單。

1. Bytea

# db/migrate/20140207133952_create_documents.rb
create_table :documents do |t|
t.binary 'payload'
end
# app/models/document.rb
class Document < ApplicationRecord
end
# Usage
data = File.read(Rails.root + "tmp/output.pdf")
Document.create payload: data

2. 陣列

# db/migrate/20140207133952_create_books.rb
create_table :books do |t|
t.string 'title'
t.string 'tags', array: true
t.integer 'ratings', array: true
end
add_index :books, :tags, using: 'gin'
add_index :books, :ratings, using: 'gin'
# app/models/book.rb
class Book < ApplicationRecord
end
# Usage
Book.create title: "Brave New World",
tags: ["fantasy", "fiction"],
ratings: [4, 5]

## Books for a single tag
Book.where("'fantasy' = ANY (tags)")

## Books for multiple tags
Book.where("tags @> ARRAY[?]::varchar[]", ["fantasy", "fiction"])

## Books with 3 or more ratings
Book.where("array_length(ratings, 1) >= 3")

3. Hstore

需要啟用 hstore 擴充功能才能使用 hstore

# db/migrate/20131009135255_create_profiles.rb
class CreateProfiles < ActiveRecord::Migration[7.0]
enable_extension 'hstore' unless extension_enabled?('hstore')
create_table :profiles do |t|
t.hstore 'settings'
end
end
# app/models/profile.rb
class Profile < ApplicationRecord
end
irb> Profile.create(settings: { "color" => "blue", "resolution" => "800x600" })

irb> profile = Profile.first
irb> profile.settings
=> {"color"=>"blue", "resolution"=>"800x600"}

irb> profile.settings = {"color" => "yellow", "resolution" => "1280x1024"}
irb> profile.save!

irb> Profile.where("settings->'color' = ?", "yellow")
=> #<ActiveRecord::Relation [#<Profile id: 1, settings: {"color"=>"yellow", "resolution"=>"1280x1024"}>]>

4. JSON 和 JSONB

# db/migrate/20131220144913_create_events.rb
# ... for json datatype:
create_table :events do |t|
t.json 'payload'
end
# ... or for jsonb datatype:
create_table :events do |t|
t.jsonb 'payload'
end
# app/models/event.rb
class Event < ApplicationRecord
end
irb> Event.create(payload: { kind: "user_renamed", change: ["jack", "john"]})

irb> event = Event.first
irb> event.payload
=> {"kind"=>"user_renamed", "change"=>["jack", "john"]}

## Query based on JSON document
# The -> operator returns the original JSON type (which might be an object), whereas ->> returns text
irb> Event.where("payload->>'kind' = ?", "user_renamed")

5. 範圍類型

此類型對應到 Ruby Range 物件。

# db/migrate/20130923065404_create_events.rb
create_table :events do |t|
t.daterange 'duration'
end
# app/models/event.rb
class Event < ApplicationRecord
end
irb> Event.create(duration: Date.new(2014, 2, 11)..Date.new(2014, 2, 12))

irb> event = Event.first
irb> event.duration
=> Tue, 11 Feb 2014...Thu, 13 Feb 2014

## All Events on a given date
irb> Event.where("duration @> ?::date", Date.new(2014, 2, 12))

## Working with range bounds
irb> event = Event.select("lower(duration) AS starts_at").select("upper(duration) AS ends_at").first

irb> event.starts_at
=> Tue, 11 Feb 2014
irb> event.ends_at
=> Thu, 13 Feb 2014

6. 複合類型

目前沒有特別支援複合類型,它們對應到一般的文字欄位。

CREATE TYPE full_address AS
(
city VARCHAR(90),
street VARCHAR(90)
);
# db/migrate/20140207133952_create_contacts.rb
execute <<-SQL
CREATE TYPE full_address AS
(
city VARCHAR(90),
street VARCHAR(90)
);
SQL
create_table :contacts do |t|
t.column :address, :full_address
end
# app/models/contact.rb
class Contact < ApplicationRecord
end
irb> Contact.create address: "(Paris,Champs-Élysées)"
irb> contact = Contact.first
irb> contact.address
=> "(Paris,Champs-Élysées)"
irb> contact.address = "(Paris,Rue Basse)"
irb> contact.save!

7. 列舉類型

類型可以對應到一般的文字欄位,或對應到 ActiveRecord::Enum

# db/migrate/20131220144913_create_articles.rb
def change
create_enum :article_status, ["draft", "published", "archived"]

create_table :articles do |t|
t.enum :status, enum_type: :article_status, default: "draft", null: false
end
end

你也可以建立列舉類型,並將列舉欄位新增到現有的資料表。

# db/migrate/20230113024409_add_status_to_articles.rb
def change
create_enum :article_status, ["draft", "published", "archived"]

add_column :articles, :status, :enum, enum_type: :article_status, default: "draft", null: false
end

上述的遷移都是可逆的,但你可以在需要時定義個別的 #up 和 #down 方法。請務必在刪除列舉類型之前移除任何依賴於該列舉類型的欄位或資料表。

def down
drop_table :articles

# OR: remove_column :articles, :status
drop_enum :article_status
end

在模型中宣告列舉屬性會新增輔助方法,並防止將無效值指定給類別的執行個體。

# app/models/article.rb
class Article < ApplicationRecord
enum :status, {
draft: "draft", published: "published", archived: "archived"
}, prefix: true
end
irb> article = Article.create
irb> article.status
=> "draft" # default status from PostgreSQL, as defined in migration above

irb> article.status_published!
irb> article.status
=> "published"

irb> article.status_archived?
=> false

irb> article.status = "deleted"
ArgumentError: 'deleted' is not a valid status

要重新命名列舉,你可以使用 rename_enum,並同時更新任何模型用法。

# db/migrate/20150718144917_rename_article_status.rb
def change
rename_enum :article_status, to: :article_state
end

要新增新值,你可以使用 add_enum_value

# db/migrate/20150720144913_add_new_state_to_articles.rb
def up
add_enum_value :article_state, "archived" # will be at the end after published
add_enum_value :article_state, "in review", before: "published"
add_enum_value :article_state, "approved", after: "in review"
end

列舉值無法刪除,這也表示 add_enum_value 是不可逆的。

若要重新命名值,可以使用 rename_enum_value

# db/migrate/20150722144915_rename_article_state.rb
def change
rename_enum_value :article_state, from: "archived", to: "deleted"
end

提示:若要顯示所有列舉的所有值,可以在 bin/rails db 或 psql 主控台中呼叫此查詢。

SELECT n.nspname AS enum_schema,
t.typname AS enum_name,
e.enumlabel AS enum_value
FROM pg_type t
JOIN pg_enum e ON t.oid = e.enumtypid
JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace

8. UUID

如果你使用的是早於版本 13.0 的 PostgreSQL,你可能需要啟用特殊擴充功能才能使用 UUID。啟用 pgcrypto 擴充功能(PostgreSQL >= 9.4)或 uuid-ossp 擴充功能(適用於更早的版本)。

# db/migrate/20131220144913_create_revisions.rb
create_table :revisions do |t|
t.uuid :identifier
end
# app/models/revision.rb
class Revision < ApplicationRecord
end
irb> Revision.create identifier: "A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11"

irb> revision = Revision.first
irb> revision.identifier
=> "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"

你可以在遷移中使用 uuid 類型來定義參考。

# db/migrate/20150418012400_create_blog.rb
enable_extension 'pgcrypto' unless extension_enabled?('pgcrypto')
create_table :posts, id: :uuid

create_table :comments, id: :uuid do |t|
# t.belongs_to :post, type: :uuid
t.references :post, type: :uuid
end
# app/models/post.rb
class Post < ApplicationRecord
has_many :comments
end
# app/models/comment.rb
class Comment < ApplicationRecord
belongs_to :post
end

9. 位元字串類型

# db/migrate/20131220144913_create_users.rb
create_table :users, force: true do |t|
t.column :settings, "bit(8)"
end
# app/models/user.rb
class User < ApplicationRecord
end
irb> User.create settings: "01010011"
irb> user = User.first
irb> user.settings
=> "01010011"
irb> user.settings = "0xAF"
irb> user.settings
=> "10101111"
irb> user.save!

10. 網路位址類型

inet 和 cidr 類型會對應到 Ruby IPAddr 物件。macaddr 類型會對應到一般文字。

# db/migrate/20140508144913_create_devices.rb
create_table(:devices, force: true) do |t|
t.inet 'ip'
t.cidr 'network'
t.macaddr 'address'
end
# app/models/device.rb
class Device < ApplicationRecord
end
irb> macbook = Device.create(ip: "192.168.1.12", network: "192.168.2.0/24", address: "32:01:16:6d:05:ef")

irb> macbook.ip
=> #<IPAddr: IPv4:192.168.1.12/255.255.255.255>

irb> macbook.network
=> #<IPAddr: IPv4:192.168.2.0/255.255.255.0>

irb> macbook.address
=> "32:01:16:6d:05:ef"

11. 幾何類型

除了 points 之外,所有幾何類型都會對應到一般文字。點會轉換為包含 x 和 y 座標的陣列。

12. 間隔

此類型會對應到 ActiveSupport::Duration 物件。

# db/migrate/20200120000000_create_events.rb
create_table :events do |t|
t.interval 'duration'
end
# app/models/event.rb
class Event < ApplicationRecord
end
irb> Event.create(duration: 2.days)

irb> event = Event.first
irb> event.duration
=> 2 days

https://chihyuanyip.github.io/2024/05/05/using-postgresql-specific-data-types-in-the-ruby-on-rails-framework.html

留言0
查看全部
avatar-img
發表第一個留言支持創作者!
Chih-Yuan Yip的沙龍 的其他內容
什麼是 CSS 預處理器 CSS 預處理器是一種工具,使用專門的語法來生成 CSS 樣式表。市面上有許多 CSS 預處理器可供選擇,它們通常會提供一些純 CSS 中沒有的功能,例如變數、混入、巢狀選擇器等。這些功能可以使 CSS 結構更易讀、更易於維護,並提高開發效率。 為什麼使用 CSS 預處
什麼是 CSS 預處理器 CSS 預處理器是一種工具,使用專門的語法來生成 CSS 樣式表。市面上有許多 CSS 預處理器可供選擇,它們通常會提供一些純 CSS 中沒有的功能,例如變數、混入、巢狀選擇器等。這些功能可以使 CSS 結構更易讀、更易於維護,並提高開發效率。 為什麼使用 CSS 預處
你可能也想看
Google News 追蹤
Thumbnail
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
11/20日NVDA即將公布最新一期的財報, 今天Sell Side的分析師, 開始調高目標價, 市場的股價也開始反應, 未來一週NVDA將重新回到美股市場的焦點, 今天我們要分析NVDA Sell Side怎麼看待這次NVDA的財報預測, 以及實際上Buy Side的倉位及操作, 從
Thumbnail
Hi 大家好,我是Ethan😊 相近大家都知道保濕是皮膚保養中最基本,也是最重要的一步。無論是在畫室裡長時間對著畫布,還是在旅途中面對各種氣候變化,保持皮膚的水分平衡對我來說至關重要。保濕化妝水不僅能迅速為皮膚補水,還能提升後續保養品的吸收效率。 曾經,我的保養程序簡單到只包括清潔和隨意上乳液
Thumbnail
每年 1 月,我跟同事都會一起關注美國 CES 展,不同地方在於,他主要看有什麼創新產品可買,我則是對物聯網(Internet of Things,下簡稱IoT)感興趣,美國 CES 展堪稱「科技風向球」,不少科技龍頭都在此展示與 IoT 趨勢相關之未來方向。
Thumbnail
如果問一位學齡前的孩子,期不期待明天的到來?得到的答案多半是肯定的,而且還能興高采烈地訴說著他們的期待與計畫,但隨著年齡的增長也伴隨著責任義務的增加,「活著」成為了一件苦差事,也讓人不斷地自問:「到底活著的意義是什麼?」 日本小說家朝井遼透過六篇各自獨立的短篇小說讓我們有機會深入去理解不同人物
Thumbnail
其實我在閱讀這本書之前並不知道是一本什麼樣的作品,單純是書名很長又帶有韓國風味,於是引發了閱讀的興趣,沒想到才翻開這本書就彷彿搭上了一輛高速行駛的列車,劇情緊湊令人屏息,全書有四篇小說都是以主人翁「映」所串成,抒發男同志在成長經歷中面對友情、愛情、親情的悲喜交集,可以感受到作者想要表達主人翁對於
Thumbnail
我從小就是一個自動自發會寫好功課並認真讀書的好學生,甚至連寒暑假作業都不會拖到最後一刻才完成,而是先把所有的作業大致看過一遍,在閒暇之餘安排時間慢慢完成,因為我不喜歡手忙腳亂的感受,總是會提早做好安排讓自己有更多餘裕去完成該做的事情。 或許是未雨綢繆的個性使然,在進入職場後也總是提前完成工作,
Thumbnail
環遊世界分享會​時答應大家會用文字陸續回答問題,今天來聊聊這題。 有想過會走那麼久嗎?在一起最有趣的一件事情是...?
Thumbnail
當我們訴說自己的生活充滿壓力時,薩古魯說:「只有在你無法掌控自己的各個系統時,你才會有壓力。」 當我們終其一生都在尋覓靈魂伴侶時,薩古魯又說:「靈魂不需要任何伴侶,因為它是絕對的、無限的。唯獨有限的事物才需要一個伴侶讓它感覺好過一些。」 無論你是否認同以上這兩段話,都可以試著翻開這本書。因為
Thumbnail
參觀完很有歷史的修道院後,看看也差不多該吃晚餐了,於是三人開始在奧爾堡這個小鎮裡覓食,在修道院附近看到這樣的人工沙灘,沙灘區裡還有不少沙灘椅,可供有小孩的家庭休憩娛樂,這個城市也太貼心了。 在邊走邊尋找用餐餐廳時,走到哪都覺得挺美的,而且此時佈滿棉花糖般白雲的藍天再現,讓城市更美麗。
Thumbnail
《不老騎士》不只是一部講述環島故事的紀錄片,更是在告訴我們一些生命道理。 年輕人自我挑戰的旅行,是想要告訴新世代不要害怕突破自我,對象就是年輕人;而這些長輩在說的,更像是不要放棄生活的希望與動力——可是年輕人終究也會有老去的一天,到時候還能保持對生活的希望嗎?
Thumbnail
你知道參加兩廳院夏日爵士音樂會,就可以憑券兌換由格蘭菲迪調製「夏日爵士限定特調」一杯(180ml)嗎?我不知道為何聽爵士總是要來個一杯才顯得groovy,但這真的很有用。 譬如我每次在家總習慣把燈調暗,只剩下一兩盞昏黃的鎢絲燈泡,從櫃子裡取出飽經風霜的唱片,溫柔地放上唱盤,音樂慢慢流洩出來
Thumbnail
上一篇分享提到在丹麥要拿到永久居留證的規定困難,一個外來的新住民要拿到丹麥的永居著實不容易,拿到了永久居留就等於像是有了美國綠卡一樣,你就不必一直換簽證,只要遵守一年要居住在丹麥固定的時間 (好像是要超過 183 天以上) 就可以。 但拿到了永久居留還不是公民,以前的丹麥是只承認單一國籍國家
Thumbnail
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
11/20日NVDA即將公布最新一期的財報, 今天Sell Side的分析師, 開始調高目標價, 市場的股價也開始反應, 未來一週NVDA將重新回到美股市場的焦點, 今天我們要分析NVDA Sell Side怎麼看待這次NVDA的財報預測, 以及實際上Buy Side的倉位及操作, 從
Thumbnail
Hi 大家好,我是Ethan😊 相近大家都知道保濕是皮膚保養中最基本,也是最重要的一步。無論是在畫室裡長時間對著畫布,還是在旅途中面對各種氣候變化,保持皮膚的水分平衡對我來說至關重要。保濕化妝水不僅能迅速為皮膚補水,還能提升後續保養品的吸收效率。 曾經,我的保養程序簡單到只包括清潔和隨意上乳液
Thumbnail
每年 1 月,我跟同事都會一起關注美國 CES 展,不同地方在於,他主要看有什麼創新產品可買,我則是對物聯網(Internet of Things,下簡稱IoT)感興趣,美國 CES 展堪稱「科技風向球」,不少科技龍頭都在此展示與 IoT 趨勢相關之未來方向。
Thumbnail
如果問一位學齡前的孩子,期不期待明天的到來?得到的答案多半是肯定的,而且還能興高采烈地訴說著他們的期待與計畫,但隨著年齡的增長也伴隨著責任義務的增加,「活著」成為了一件苦差事,也讓人不斷地自問:「到底活著的意義是什麼?」 日本小說家朝井遼透過六篇各自獨立的短篇小說讓我們有機會深入去理解不同人物
Thumbnail
其實我在閱讀這本書之前並不知道是一本什麼樣的作品,單純是書名很長又帶有韓國風味,於是引發了閱讀的興趣,沒想到才翻開這本書就彷彿搭上了一輛高速行駛的列車,劇情緊湊令人屏息,全書有四篇小說都是以主人翁「映」所串成,抒發男同志在成長經歷中面對友情、愛情、親情的悲喜交集,可以感受到作者想要表達主人翁對於
Thumbnail
我從小就是一個自動自發會寫好功課並認真讀書的好學生,甚至連寒暑假作業都不會拖到最後一刻才完成,而是先把所有的作業大致看過一遍,在閒暇之餘安排時間慢慢完成,因為我不喜歡手忙腳亂的感受,總是會提早做好安排讓自己有更多餘裕去完成該做的事情。 或許是未雨綢繆的個性使然,在進入職場後也總是提前完成工作,
Thumbnail
環遊世界分享會​時答應大家會用文字陸續回答問題,今天來聊聊這題。 有想過會走那麼久嗎?在一起最有趣的一件事情是...?
Thumbnail
當我們訴說自己的生活充滿壓力時,薩古魯說:「只有在你無法掌控自己的各個系統時,你才會有壓力。」 當我們終其一生都在尋覓靈魂伴侶時,薩古魯又說:「靈魂不需要任何伴侶,因為它是絕對的、無限的。唯獨有限的事物才需要一個伴侶讓它感覺好過一些。」 無論你是否認同以上這兩段話,都可以試著翻開這本書。因為
Thumbnail
參觀完很有歷史的修道院後,看看也差不多該吃晚餐了,於是三人開始在奧爾堡這個小鎮裡覓食,在修道院附近看到這樣的人工沙灘,沙灘區裡還有不少沙灘椅,可供有小孩的家庭休憩娛樂,這個城市也太貼心了。 在邊走邊尋找用餐餐廳時,走到哪都覺得挺美的,而且此時佈滿棉花糖般白雲的藍天再現,讓城市更美麗。
Thumbnail
《不老騎士》不只是一部講述環島故事的紀錄片,更是在告訴我們一些生命道理。 年輕人自我挑戰的旅行,是想要告訴新世代不要害怕突破自我,對象就是年輕人;而這些長輩在說的,更像是不要放棄生活的希望與動力——可是年輕人終究也會有老去的一天,到時候還能保持對生活的希望嗎?
Thumbnail
你知道參加兩廳院夏日爵士音樂會,就可以憑券兌換由格蘭菲迪調製「夏日爵士限定特調」一杯(180ml)嗎?我不知道為何聽爵士總是要來個一杯才顯得groovy,但這真的很有用。 譬如我每次在家總習慣把燈調暗,只剩下一兩盞昏黃的鎢絲燈泡,從櫃子裡取出飽經風霜的唱片,溫柔地放上唱盤,音樂慢慢流洩出來
Thumbnail
上一篇分享提到在丹麥要拿到永久居留證的規定困難,一個外來的新住民要拿到丹麥的永居著實不容易,拿到了永久居留就等於像是有了美國綠卡一樣,你就不必一直換簽證,只要遵守一年要居住在丹麥固定的時間 (好像是要超過 183 天以上) 就可以。 但拿到了永久居留還不是公民,以前的丹麥是只承認單一國籍國家