Tìm hiểu thêm về gem Ancestry
Bài đăng này đã không được cập nhật trong 7 năm
Đôi khi trong công việc bạn phải động đến dữ liệu dạng cây thư mục, gem Ancestry
hỗ trợ khá tốt vấn đề này, việc hiểu rõ hơn về gem này giúp bạn chủ động hơn trong công việc
Link: https://github.com/stefankroes/ancestry
Gem Ancestry
khá giống gem Paranoia
, nghĩa laf cũng tạo thêm 1 method trong ActiveRecord::Base
class << ActiveRecord::Base
def has_ancestry options = {}
...
end
end
Ngoài console
ta có thể add ancestry
cho 1 model 1 cách dynamically
bằng cách gọi hàm has_ancestry
Order.has_ancestry
=> [Order(id: integer, course_order_id: ...
2.3.0 :012 > Order.roots
Order Load (18.7ms) SELECT "orders".* FROM "orders" WHERE "orders"."deleted_at" IS NULL AND "orders"."ancestry" IS NULL
ActiveRecord::StatementInvalid: PG::UndefinedColumn: ERROR: column orders.ancestry does not exist
Tuy nhiên do options
đưa vào chưa có chỉ định ancestry_column
nên câu query chưa đúng, gỉa sử ta dùng cột uid
(cột phải có dạng string
) là cột lưu ancestry_column
Order.has_ancestry ancestry_column: :uid
=> [Order(id: integer, course_order_id: integer...
Order.roots
Order Load (0.5ms) SELECT "orders".* FROM "orders" WHERE "orders"."deleted_at" IS NULL AND "orders"."uid" IS NULL
Ta thấy câu query
đã đúng, cột lưu ancestry_column
đã được chỉ định trong câu query. Ta có thể lấy tên cột này bằng cáh gọi ancestry_column
Order.ancestry_column
=> :uid
Các câu query
của gem Ancestry
: https://github.com/stefankroes/ancestry/blob/master/lib/ancestry/has_ancestry.rb#L43
scope :roots, lambda { where(ancestry_column => nil) }
scope :ancestors_of, lambda { |object| where(ancestor_conditions(object)) }
scope :path_of, lambda { |object| where(path_conditions(object)) }
scope :children_of, lambda { |object| where(child_conditions(object)) }
scope :descendants_of, lambda { |object| where(descendant_conditions(object)) }
scope :subtree_of, lambda { |object| where(subtree_conditions(object)) }
scope :siblings_of, lambda { |object| where(sibling_conditions(object)) }
scope :ordered_by_ancestry, lambda {
if %w(mysql mysql2 sqlite postgresql).include?(connection.adapter_name.downcase) && ActiveRecord::VERSION::MAJOR >= 5
reorder("coalesce(#{connection.quote_table_name(table_name)}.#{connection.quote_column_name(ancestry_column)}, '')")
else
reorder("(CASE WHEN #{connection.quote_table_name(table_name)}.#{connection.quote_column_name(ancestry_column)} IS NULL THEN 0 ELSE 1 END), #{connection.quote_table_name(table_name)}.#{connection.quote_column_name(ancestry_column)}")
end
}
scope :ordered_by_ancestry_and, lambda { |order|
if %w(mysql mysql2 sqlite postgresql).include?(connection.adapter_name.downcase) && ActiveRecord::VERSION::MAJOR >= 5
reorder("coalesce(#{connection.quote_table_name(table_name)}.#{connection.quote_column_name(ancestry_column)}, ''), #{order}")
else
reorder("(CASE WHEN #{connection.quote_table_name(table_name)}.#{connection.quote_column_name(ancestry_column)} IS NULL THEN 0 ELSE 1 END), #{connection.quote_table_name(table_name)}.#{connection.quote_column_name(ancestry_column)}, #{order}")
end
}
scope :path_of, lambda { |object| to_node(object).path }
Hàm ancestor_conditions
nằm tại https://github.com/stefankroes/ancestry/blob/master/lib/ancestry/instance_methods.rb#L110
Tại console
ta có thể gọi như sau:
Order.first.ancestor_conditions
Order Load (1.3ms) SELECT "orders".* FROM "orders" WHERE "orders"."deleted_at" IS NULL ORDER BY "orders"."id" ASC LIMIT 1
=> #<Arel::Nodes::In:0x0000000987a4c0 @left=#<struct Arel::Attributes::Attribute relation=#<Arel::Table:0x0000000640d980 @name="orders", @engine=Order(id: integer, course_order_id: integer, user_id: integer, created_at: datetime, updated_at: datetime, data_import_result_id: integer, deleted_at: datetime, total: decimal, item_total: decimal, shipment_total: decimal, payment_total: decimal, adjustment_total: decimal, tax_total: decimal, status: integer, uid: string, staff_id: integer, order_merge_bundler_id: integer, order_split_bundler_id: integer, canceled_on: date, ordered_on: date, author_id: integer, order_type_id: integer, device_kind: integer, is_latest: boolean, previous_uid: string, is_allocated: boolean, shop_id: integer, continuation_times: integer, currency_id: integer, lock_version: integer), @columns=nil, @aliases=[], @table_alias=nil, @primary_key=nil>, name=:id>, @right=[#<Arel::Nodes::Casted:0x0000000987a4e8 @val=0, @attribute=#<struct Arel::Attributes::Attribute relation=#<Arel::Table:0x0000000640d980 @name="orders", @engine=Order(id: integer, course_order_id: integer, user_id: integer, created_at: datetime, updated_at: datetime, data_import_result_id: integer, deleted_at: datetime, total: decimal, item_total: decimal, shipment_total: decimal, payment_total: decimal, adjustment_total: decimal, tax_total: decimal, status: integer, uid: string, staff_id: integer, order_merge_bundler_id: integer, order_split_bundler_id: integer, canceled_on: date, ordered_on: date, author_id: integer, order_type_id: integer, device_kind: integer, is_latest: boolean, previous_uid: string, is_allocated: boolean, shop_id: integer, continuation_times: integer, currency_id: integer, lock_version: integer), @columns=nil, @aliases=[], @table_alias=nil, @primary_key=nil>, name=:id>>]>
Gía trị trả về dạng Arel
, mình đã có 1 bàn viết sơ qua về dạng này (https://viblo.asia/pham.huy.cuong/posts/7prv31LkMKod)
Các hàm ancestor_conditions, path_conditions, child_conditions, descendant_conditions, subtree_conditions, sibling_conditions
được đặt tại https://github.com/stefankroes/ancestry/blob/master/lib/ancestry/instance_methods.rb và đều có thể gọi theo cách trên, gía trị trả về là dạng Arel
.
Cột ancestry_column
được validate format ở: https://github.com/stefankroes/ancestry/blob/master/lib/ancestry/has_ancestry.rb#L37
validates_format_of ancestry_column, :with => Ancestry::ANCESTRY_PATTERN, :allow_nil => true
Ancestry::ANCESTRY_PATTERN
: /\A[0-9]+(\/[0-9]+)*\Z/
Validate
xem id của record
có thuộc mảng id
những record
cha của nó (phần này nhằm tránh vòng lặp vô cùng, ví dụ gía trị của ancestry_column
là 1/4/8/9
mà id của record
là 4 thì sẽ raise
ra lỗi): https://github.com/stefankroes/ancestry/blob/master/lib/ancestry/has_ancestry.rb#L40
validate :ancestry_exclude_self
Hàm ancestry_exclude_self
nằm tại: https://github.com/stefankroes/ancestry/blob/master/lib/ancestry/instance_methods.rb#L4
def ancestry_exclude_self
errors.add(:base, "#{self.class.name.humanize} cannot be a descendant of itself.") if ancestor_ids.include? self.id
end
Ví dụ đối với model
StockLocation
:
StockLocation.last
StockLocation Load (0.6ms) SELECT "stock_locations".* FROM "stock_locations" ORDER BY "stock_locations"."id" DESC LIMIT 1
=> #<StockLocation id: 36, name: "Local 36", code: "A3600", ancestry: "30/34", priority: 36, warehouse_id: 1, created_at: "2017-01-16 07:49:24", updated_at: "2017-01-16 07:49:24", is_folder: false, position: 2>
> StockLocation.last.valid?
StockLocation Load (1.1ms) SELECT "stock_locations".* FROM "stock_locations" ORDER BY "stock_locations"."id" DESC LIMIT 1
=> true
Ta thử set
lại gía trị cội ancestry
với gía trị bao gồm id
của record này
stock_location = StockLocation.last
StockLocation Load (1.0ms) SELECT "stock_locations".* FROM "stock_locations" ORDER BY "stock_locations"."id" DESC LIMIT 1
=> #<StockLocation id: 36, name: "Local 36", code: "A3600", ancestry: "30/34", priority: 36, warehouse_id: 1, created_at: "2017-01-16 07:49:24", updated_at: "2017-01-16 07:49:24", is_folder: false, position: 2>
> stock_location.ancestry = "30/36/34"
=> "30/36/34"
> stock_location.valid?
=> false
Cảm ơn và hi vọng bài viết có ích trong công việc của bạn.
All rights reserved