【问题标题】:Access JSON field of join table with Active Record使用 Active Record 访问连接表的 JSON 字段
【发布时间】:2019-09-24 21:53:21
【问题描述】:

一个店铺has_many产品和一个产品belongs_to一个店铺。 该商店有opening_hoursclosing_hours 列。每列存储一个哈希,如下所示:

opening_hours = {'Monday' => '12:00', 'Tuesday' => '13:00'}

等等

我正在使用当前日期和时间,我想检索所有商店的opening_hours 小于当前时间(小于closing_hours)的产品。 基本上是开店时能买到的所有产品。

我在控制器中做了类似的事情:

day_today = Date.today.strftime('%A')
time_now = Time.new.strftime('%H:%M')
@products = Product.joins(shop: [{opening_hours[day_today] < time_now, 
                                 {closing_hours[day_today] > time_now }])

编辑

这是在数据库中存储开放时间的代码

hours_table = shop.search('table')
    opening_hours = {}
    closing_hours = {}
    hours_table.first.search('tr').each do |tr|
      cells = tr.search('td')
      day = cells.first.text.strip
      hours = cells.last.text.strip.tr('-', '').split
      opening_hours[day] = hours[0].to_time.strftime('%H:%M')
      closing_hours[day] = hours[1].to_time.strftime('%H:%M')
    end

shop = Shop.new(
        name: shop_name,
        opening_hours: opening_hours,
        closing_hours: closing_hours
      )
      shop.save

      new_product = Product.new(
        name: foo,
        description: foo,
        price: foo,
        company: 'foo',
      )
      new_product.shop = shop
      new_product.save

这是为 Shop 添加营业时间的迁移。关闭时间也是如此。

class AddOpeningHoursToShop < ActiveRecord::Migration[5.1]
  def change
    add_column :shops, :opening_hours, :json, default: {}
  end
end

这是将商店 ID 添加到产品的迁移

class AddShopToProducts < ActiveRecord::Migration[5.1]
  def change
    add_reference :products, :shop, foreign_key: true
  end
end

你猜到了吗?

【问题讨论】:

  • 开盘和关盘时间是如何实际存储在数据库中的?作为每天的时间戳?作为字符串?周末或节假日呢?你会考虑到它们吗?
  • 在我上面添加的代码中,我将它们转换为时间,但它们在终端中显示为字符串:{"Monday" => "12:00", "Tuesday" => "13:00" }
  • 你有这些记录的例子吗? Product 和 Shop 的迁移是如何定义的?
  • 刚刚添加了更多细节

标签: ruby-on-rails ruby postgresql activerecord


【解决方案1】:

您可以使用-&gt;&gt; 运算符以文本形式访问特定的 JSON 字段:

SELECT opening_hours->>'Monday' AS monday_opening_hour FROM shops;

closing_hours 也一样,只是更改了列和字段的名称。

因此,您的 AR 表示形式如下:

Product
  .joins(:shop)
  .where(
    "shops.opening_hours->>:day <= :now AND shops.closing_hours->>:day >= :now",
    day: day_today,
    now: time_now
  )

您可以轻松绑定密钥名称和您要请求的时间,此处为daynow

注意,不需要在表名前显式添加,但我这样做只是为了清楚起见。


这也可以使用 BETWEEN 运算符来完成:

Product
  .joins(:shop)
  .where(
    ":now BETWEEN shops.opening_hours->>:day AND shops.closing_hours->>:day",
    day: day_today,
    now: time_now
  )

你可以使用你认为更清晰的那个。

在任何情况下,您都可以通过使用 PostgreSQL 的 NOW 函数摆脱存储 time_now 变量。如果您将其转换为文本(NOW()::text),它将处理时间格式差异:

Product
  .joins(:shop)
  .where(
    "NOW()::text BETWEEN shops.opening_hours->>:day AND shops.closing_hours->>:day",
    day: day_today
  )

同样,也可以从NOW获取日期。您可以为此使用to_char(NOW(), 'Day')

SELECT to_char(date, 'Day') AS day
FROM generate_series (NOW() - '6 days'::interval, NOW(), '1 day') date;

    day
-----------
 Thursday
 Friday
 Saturday
 Sunday
 Monday
 Tuesday
 Wednesday

所以:

Product
  .joins(:shop)
  .where("NOW()::text BETWEEN shops.opening_hours->>(to_char(NOW(), 'Day'))
                          AND shops.closing_hours->>(to_char(NOW(), 'Day'))")

【讨论】:

  • 非常感谢你!从所有解决方案中,只有第一个对我有用。第三个使我的终端出现故障,最后一个返回一个空数组。但再次感谢,真的很感激 :))
猜你喜欢
  • 2013-01-01
  • 2020-05-16
  • 1970-01-01
  • 2014-09-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多