ransack
是一个基於Model
层级的Gem
,使用Ransack
,可以将原本的Sql Statement
写得更简洁。Ransack
为Rails
数一数二好用的Gem
,也因此有许多人帮忙为ransack
背书。
Ransack
会依据传过来的参数,自动SubQuery
、Join
、Preload
,因此复杂的问题我们可以使用Ransack
语句来处理,更复杂的问题,或者enum
的查询,可以交给自定义的Ransacker
,在Ransacker
里头使用ransack
自定义搜寻,做成更复杂的搜寻系统。
使用ransack
,就像是使用sql statement
一样,我们只要知道会遇到哪些雷,知道哪些雷不要踩後,就可以很舒服的使用。
查找资料最需要用实例来解释,以下列出几种曾经几个使用过的ransack
语句。
_cont
为模糊搜寻的意思Product.ransack(title_zh_cont: '釉彩').result
SELECT `products`.* FROM `products` WHERE `products`.`title_zh` LIKE '%釉彩%' LIMIT 11
搜寻子订单内的订单项目对应的商品名称
variant
,variant
又关联product
,而我们要找product
的title_zh
栏位,因此写成variant_product_title_zh_cont
ransack
自动帮我们用left_join
起来sub_order.order_items.ransack(variant_product_title_zh_cont: '珍珠').result
SELECT `order_items`.* FROM `order_items` LEFT OUTER JOIN `variants` ON `variants`.`id` = `order_items`.`variant_id` LEFT OUTER JOIN `products` ON `products`.`id` = `variants`.`product_id` WHERE `order_items`.`sub_order_id` = 1 AND `products`.`title_zh` LIKE '%珍珠%' LIMIT 11
找寻一个礼拜前创建的订单、不能有任一张退货单为申请中、不能为全部的退货单状态为「审查不同意」
_does_not_match_any
:没有任何一个符合_does_not_match_all
:没有全部都符合lt
/gt
/lteq
/ gteq
:小於、大於、小於等於、大於等於Order.instance_eval do
# 排除尚在审查的退货单的订单
scope :return_order_reviewed, -> { ransack(return_orders_status_does_not_match_any: :applied).result }
# 一个礼拜前
scope :week_ago, -> { ransack(done_at_lt: Tool.appreciation_period.ago).result }
# 若该订单底下的所有退货单审查不通过,则不用同步pos
scope :not_all_return_failed, -> { ransack(return_orders_status_does_not_match_all: :return_failed).result }
end
Order.return_order_reviewed.week_ago.not_all_return_failed
SELECT `orders`.* FROM `orders` WHERE `orders`.`id` NOT IN (SELECT `return_orders`.`order_id` FROM `return_orders` WHERE `return_orders`.`order_id` = `orders`.`id` AND NOT ((`return_orders`.`status` NOT LIKE 0))) AND `orders`.`done_at` < '2021-09-27 14:54:23' AND `orders`.`id` NOT IN (SELECT `return_orders`.`order_id` FROM `return_orders` WHERE `return_orders`.`order_id` = `orders`.`id` AND NOT ((`return_orders`.`status` NOT LIKE 4))) LIMIT 11
以下为判断最新购物车内物品是否为空,以及车里面的项目是否在1天内更新过的语法。由SQL Statement
可以知道,两者的意思相同,cart_items_not_null: true
是多余的,理由也很好理解,因为没有购物车项目就不会找到1天内更新过的购物车项目
_null
:空值_not_null
:非空值Cart.ransack(cart_items_updated_at_lteq: 1.days.ago, cart_items_not_null: true).result.to_sql
#=> "SELECT `carts`.* FROM `carts` LEFT OUTER JOIN `cart_items` ON `cart_items`.`cart_id` = `carts`.`id` WHERE `cart_items`.`updated_at` <= '2021-09-08 06:10:04.641275'"
Cart.ransack(cart_items_updated_at_lteq: 1.days.ago).result.to_sql
#=> "SELECT `carts`.* FROM `carts` LEFT OUTER JOIN `cart_items` ON `cart_items`.`cart_id` = `carts`.`id` WHERE `cart_items`.`updated_at` <= '2021-09-08 06:10:14.869951'"
另外关於这台车,还有值得讨论的地方。
以下转换为Sql Statement
时,使用的是LEFT (OUTER) JOIN
语法 ,上一回提过搜寻的结果会造成找出的结果有重复的笔数,因此我们需要用distinct
语法来滤除。当时购物车没有使用distinct
而导致对某使用者重复推播,所以回过头还是要注意Sql Statement
是否会有找到重复资料的问题
Cart.ransack(cart_items_updated_at_gt: 1.days.ago, customer_id_null: false).result.count
#=> (1.1ms) SELECT COUNT(*) FROM `carts` LEFT OUTER JOIN `cart_items` ON `cart_items`.`cart_id` = `carts`.`id` WHERE (`cart_items`.`updated_at` > '2021-09-23 23:29:34.891970' AND `carts`.`customer_id` IS NOT NULL)
#=> 10
Cart.ransack(cart_items_updated_at_gt: 1.days.ago, customer_id_null: false).result.uniq.count
#=> Cart Load (2.7ms) SELECT `carts`.* FROM `carts` LEFT OUTER JOIN `cart_items` ON `cart_items`.`cart_id` = `carts`.`id` WHERE (`cart_items`.`updated_at` > '2021-09-23 23:29:42.348931' AND `carts`.`customer_id` IS NOT NULL)
#=> 2
#============== 使用distinct
Cart.ransack(cart_items_updated_at_gt: 1.days.ago, customer_id_null: false).result.distinct.count
#=> (4.6ms) SELECT COUNT(DISTINCT `carts`.`id`) FROM `carts` LEFT OUTER JOIN `cart_items` ON `cart_items`.`cart_id` = `carts`.`id` WHERE (`cart_items`.`updated_at` > '2021-09-23 23:38:08.748560' AND `carts`.`customer_id` IS NOT NULL)
#=> 2
⭐️ ⭐️ ⭐️ ⭐️ Ransack
所需要知道的雷,只会发生於1对多has_many
,当被left_join
, join
时可能会出现重复笔以外,其他用法基本上都毋需太过担心。
⭐️ 搜寻条件有开始、结束时间、关键字、下拉式
SELECT `blogs`.* FROM `blogs` WHERE ((`blogs`.`title` LIKE '%标题%' OR `blogs`.`content` LIKE '%标题%') AND `blogs`.`created_at` >= '2020-09-26 16:00:00' ANlogs`.`created_at` < '2021-09-27 16:00:00') LIMIT 10 OFFSET 0
⭐️ 搜寻条件只有开始和结束时间和关键字
SELECT `blogs`.* FROM `blogs` WHERE ((`blogs`.`title` LIKE '%标题%' OR `blogs`.`content` LIKE '%标题%') AND `blogs`.`created_at` >= '2020-09-26 16:00:00' ANlogs`.`created_at` < '2021-09-27 16:00:00') LIMIT 10 OFFSET 0
⭐️ 搜寻条件只有开始和结束时间
SELECT `blogs`.* FROM `blogs` WHERE (`blogs`.`created_at` >= '2020-09-26 16:00:00' AND `blogs`.`created_at` < '2021-09-27 16:00:00') LIMIT 10 OFFSET 0
⭐️ ⭐️ ⭐️ ⭐️ 前面提到过,只要下的关联为belongs_to
, has_one
,都不用担心会有查询重复笔数的问题。
ReturnOrder.ransack(m: 'or', return_actual_rebate_amount_lteq: 0, return_actual_rebate_amount_null: true).result
class Post < ActiveRecord::Base
belongs_to :author
# Abbreviate :author_first_name_or_author_last_name to :author
ransack_alias :author, :author_first_name_or_author_last_name
end
ransack
搭配 enum
的用法
# enum
class SubOrder < ApplicationRecord
enum sex: { male: 'M', female: 'F', unavailable: 'N' }
ransacker :sex, formatter: proc { |v| sexes[v] }
# ransacker :vip_level, formatter: proc { |v| VIP_LEVEL_TRANS_MAP[v] }
end
# 使用 ransacker 以前
SubOrder.ransack(sex: sexes[:male])
# 使用 ransacker 以後
SubOrder.ransack(sex: :male)
我们也可以自定义 ransack
class SubOrder < ApplicationRecord
scope :unshipped, -> (tab) do
if tab.in? [:waiting, 'waiting']
ransack(status_eq: Status::PROCESSING, shipping_status_eq: ShippingStatus::READY).result
elsif tab.in? [:failed, 'failed']
ransack(status_not_eq: Status::DONE, shipping_status_eq: ShippingStatus::FAILED).result
else
SubOrder.all
end
end
def self.ransackable_scopes(_auth_object = nil)
[:unshipped]
end
end
SubOrder.ransack(unshipped: :waiting).result.to_sql
#=> "SELECT `sub_orders`.* FROM `sub_orders` WHERE (`sub_orders`.`status` = 'processing' AND `sub_orders`.`shipping_status` = 'ready')"
class ReturnOrder < ApplicationRecord
enum status: %i(applied processing refund_failed return_failed done)
ransacker :status, formatter: proc { |v| statuses[v] }
# 待处理/退货失败/全部退货单
scope :tab, -> (tab) do
if tab.in? [:all, 'all']
all
elsif tab.in? [:return_failed, 'return_failed']
where(status: :return_failed)
else
where(status: :processing)
end
end
def self.ransackable_scopes(_auth_object = nil)
[:tab]
end
end
ReturnOrder.ransack(tab: :processing).result
#=> SELECT `return_orders`.* FROM `return_orders` WHERE `return_orders`.`status` = 1 LIMIT 11
我们可以透过ransackable_attributes
,搜寻该model
可以使用的ransack
属性。
> SubOrder.ransackable_attributes
#=> ["id", "brand_id", "order_id", "created_at", "updated_at", "number", "store_id", "shipping_status", "shipped_at", "arrived_at", "received_at", "status_manual_changed", "shipping_status_manual_changed", "failed_at", "remark", "status", "shipments_count", "pickup_personally", "admin_force_return"]
原本打算将Gem
相关的文章集结成系列介绍,但因在datatable
使用了很多次,加上我也答应我的同事要写一篇文介绍ransack
,因此有这篇文章的诞生。
什麽是资料库(database)? 想必大家都对资料有一定的认识,以我们生活中常看的电影为例,电影名...
哈罗~ 我们前几天提到, 可以利用网路监听、密码破解来取得使用权限, 今天我们要来介绍可以做远端控制...
设计大纲 上一个区块卖完小广告後,今天马上就给它塞个平台的优点进来,这样可以让使用者有「感同身受」的...
两数相加平均数 必须是被排序好的 应用在找寻有哪些配对符合所要的数值 接下来让我们实作吧!!! 回圈...
Chap.I Practical drill 实战演练 以下内容来自这里 Prat4. Run Ex...