簡體   English   中英

Rails find_by_sql datetime 屬性返回 nil 值

[英]Rails find_by_sql datetime attribute return nil value

當我執行find_by_sql查詢時,日期時間選擇返回 'nil' 作為值:

ruby:3.0.0p0 導軌:6.1.3.1 postgresql:10

架構:

  create_table "availabilities", force: :cascade do |t|
    t.bigint "timeslot_id", null: false
    t.bigint "option_id", null: false
    t.integer "qty"
    t.integer "stock"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["option_id"], name: "index_availabilities_on_option_id"
    t.index ["timeslot_id"], name: "index_availabilities_on_timeslot_id"
  end

  create_table "locales", force: :cascade do |t|
    t.string "code", null: false
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end

  create_table "option_translations", force: :cascade do |t|
    t.bigint "option_id", null: false
    t.bigint "locale_id", null: false
    t.string "name"
    t.string "description"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["locale_id"], name: "index_option_translations_on_locale_id"
    t.index ["option_id"], name: "index_option_translations_on_option_id"
  end

  create_table "options", force: :cascade do |t|
    t.bigint "variation_id", null: false
    t.bigint "vat_id", null: false
    t.string "neptis_key", null: false
    t.string "external_id"
    t.decimal "price", precision: 10, scale: 2, null: false
    t.decimal "external_price", precision: 10, scale: 2, null: false
    t.integer "min_sell_qty"
    t.integer "max_sell_qty"
    t.integer "capacity"
    t.datetime "sales_begin_at"
    t.datetime "sales_end_at"
    t.string "duration_type"
    t.string "duration_value"
    t.integer "booking_notice_period"
    t.integer "sort_index"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["neptis_key"], name: "unique_neptis_key", unique: true
    t.index ["variation_id"], name: "index_options_on_variation_id"
    t.index ["vat_id"], name: "index_options_on_vat_id"
  end

  create_table "products", force: :cascade do |t|
    t.bigint "user_ticket_office_id", null: false
    t.string "external_id", null: false
    t.string "external_ref"
    t.string "external_supplier_id", default: ""
    t.float "rating_summary", default: 0.0
    t.integer "reviews_count", default: 0
    t.jsonb "appointment_types", default: []
    t.boolean "need_appointment", default: false
    t.string "duration_type", default: "hour"
    t.jsonb "duration_values", default: []
    t.integer "booking_notice_period", default: 0
    t.jsonb "ticket_opening_times", default: []
    t.jsonb "extra"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["user_ticket_office_id"], name: "index_products_on_user_ticket_office_id"
  end

  create_table "timeslots", force: :cascade do |t|
    t.bigint "variation_id", null: false
    t.datetime "begin_at"
    t.boolean "available"
    t.integer "qty_available"
    t.integer "capacity"
    t.datetime "end_booking_at"
    t.integer "hours_before_booking"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["variation_id"], name: "index_timeslots_on_variation_id"
  end

  create_table "variation_translations", force: :cascade do |t|
    t.bigint "variation_id", null: false
    t.bigint "locale_id", null: false
    t.string "name"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["locale_id"], name: "index_variation_translations_on_locale_id"
    t.index ["variation_id"], name: "index_variation_translations_on_variation_id"
  end

  create_table "variations", force: :cascade do |t|
    t.bigint "product_id", null: false
    t.string "external_id"
    t.date "first_date"
    t.date "last_date"
    t.jsonb "available_dates"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["product_id"], name: "index_variations_on_product_id"
  end

  create_table "vats", force: :cascade do |t|
    t.float "value", null: false
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end

詢問:

module Catalog
  class AllBookingsAvailableQuery

    def self.call( user: , product:, locale: nil, begin_at: nil, end_at: nil)
      query = Catalog::AllBookingsAvailableQuery.new(  user: user, product: product, locale: locale, begin_at: begin_at, end_at: end_at )
      query.exec_sql
    end

    def initialize( user: , product: ,  locale: , begin_at: Time.now, end_at: Time.now + 3.months)
      @locale_id   = locale.id
      user.switch_tenant!
      @user_id     = user.id
      @product_id  = product.id
      @begin_at    = begin_at
      @end_at      = end_at
    end

    def exec_sql
      Availability.find_by_sql [Arel.sql(build_sql), { begin_at: @begin_at, end_at: @end_at} ]
    end

    def build_sql
      <<~SQL
        SELECT 
               (vs.id || '') as lvl0,
               (vs.id || '.' || os.id) as lvl1,
               tims.begin_at as option_event_dt,
               to_char(tims.begin_at, 'HH24' || ':' || 'MI' ) as event_hour,
               to_char(tims.begin_at, 'DD/MM/YYYY' ) as event_date,
               vs.id as variation_id, coalesce(locale_variation_trans.name, first_variation_trans.name) as variation_name,
               os.id as option_id, coalesce(locale_option_trans.name, first_option_trans.name) as option_name,
               os.price, vats.value as vat, os.min_sell_qty, os.max_sell_qty, os.sort_index,
               avs.qty, avs.stock
        FROM "user_#{@user_id}".availabilities avs
            LEFT JOIN "user_#{@user_id}".timeslots tims on tims.id = avs.timeslot_id AND tims.begin_at > :begin_at AND tims.begin_at < :end_at
            LEFT JOIN "user_#{@user_id}".options     os on os.id   = avs.option_id
            LEFT JOIN "public".vats           on os.id   = avs.option_id
            LEFT JOIN "user_#{@user_id}".variations  vs on vs.id   = os.variation_id
            LEFT JOIN LATERAL (
                SELECT vts1.name
                FROM "user_#{@user_id}".variation_translations vts1
                WHERE vts1.variation_id = vs.id AND vts1.locale_id = #{@locale_id}
                LIMIT 1
            ) locale_variation_trans on TRUE
             LEFT JOIN LATERAL (
                 SELECT vts2.name
                 FROM "user_#{@user_id}".variation_translations vts2
                 WHERE vts2.variation_id = vs.id
                 LIMIT 1
                 ) first_variation_trans on TRUE
            LEFT JOIN LATERAL (
                SELECT ots1.name
                FROM "user_#{@user_id}".option_translations ots1
                WHERE ots1.option_id = os.id AND ots1.locale_id = #{@locale_id}
                LIMIT 1
            ) as locale_option_trans on TRUE
             LEFT JOIN LATERAL (
                    SELECT ots2.name
                    FROM "user_#{@user_id}".option_translations ots2
                    WHERE ots2.option_id = os.id
                    LIMIT 1
                 ) as first_option_trans on TRUE
        WHERE  vs.product_id = #{@product_id}
      SQL
    end


  end
end

跑:

u = User.find(1)
p = Product.find(1)
l = locale.find(3)
::Catalog::AllBookingsAvailableQuery.call(  user: u, product: p, locale: l).first.attributes
# => {"lvl0"=>"1", "lvl1"=>"1.1", "option_event_dt"=>nil, "event_hour"=>nil, "event_date"=>nil, "variation_id"=>1, "variation_name"=>"Normal", "option_id"=>1, "option_name"=>"Adulte", "price"=>0.2e2, "vat"=>5.0, "min_sell_qty"=>0, "max_sell_qty"=>0, "sort_index"=>0, "qty"=>350, "stock"=>350, "id"=>nil}

如您所見,與事件日期時間相關的所有屬性都是空值。 當我在 psql 控制台中運行查詢時,我有這些日期時間: 在此處輸入圖像描述

有誰知道 append 與 find_by_sql 和缺少事件的日期時間屬性的屬性?

編輯 1 :@engineersmnky 詢問查詢(沒有 Arel.sql )。 不知道是不是租客的副作用...

irb(main):011:0> a = Catalog::AllBooking.call( user: u, product: Product.find(1), locale: Locale.find(3))
  [api_billetteries_development] [user_1]   Product Load (0.4ms)  SELECT "products".* FROM "products" WHERE "products"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]
  [api_billetteries_development] [user_1]   Locale Load (0.3ms)  SELECT "public"."locales".* FROM "public"."locales" WHERE "public"."locales"."id" = $1 LIMIT $2  [["id", 3], ["LIMIT", 1]]
  [api_billetteries_development] [user_1]   Availability Load (4.2ms)  SELECT 
       (vs.id || '') as lvl0,
       (vs.id || '.' || os.id) as lvl1,
       tims.begin_at as option_event_dt,
       to_char(tims.begin_at, 'HH24' || ':' || 'MI' ) as event_hour,
       to_char(tims.begin_at, 'DD/MM/YYYY' ) as event_date,
       vs.id as variation_id, coalesce(locale_variation_trans.name, first_variation_trans.name) as variation_name,
       os.id as option_id, coalesce(locale_option_trans.name, first_option_trans.name) as option_name,
       os.price, vats.value as vat, os.min_sell_qty, os.max_sell_qty, os.sort_index,
       avs.qty, avs.stock
FROM "user_1".availabilities avs
    LEFT JOIN "user_1".timeslots tims on tims.id = avs.timeslot_id AND tims.begin_at > NULL AND tims.begin_at < NULL
    LEFT JOIN "user_1".options     os on os.id   = avs.option_id
    LEFT JOIN "public".vats           on os.id   = avs.option_id
    LEFT JOIN "user_1".variations  vs on vs.id   = os.variation_id
    LEFT JOIN LATERAL (
        SELECT vts1.name
        FROM "user_1".variation_translations vts1
        WHERE vts1.variation_id = vs.id AND vts1.locale_id = 3
        LIMIT 1
    ) locale_variation_trans on TRUE
     LEFT JOIN LATERAL (
         SELECT vts2.name
         FROM "user_1".variation_translations vts2
         WHERE vts2.variation_id = vs.id
         LIMIT 1
         ) first_variation_trans on TRUE
    LEFT JOIN LATERAL (
        SELECT ots1.name
        FROM "user_1".option_translations ots1
        WHERE ots1.option_id = os.id AND ots1.locale_id = 3
        LIMIT 1
    ) as locale_option_trans on TRUE
     LEFT JOIN LATERAL (
            SELECT ots2.name
            FROM "user_1".option_translations ots2
            WHERE ots2.option_id = os.id
            LIMIT 1
         ) as first_option_trans on TRUE
WHERE  vs.product_id = 1

=> [#<Availability id: nil, option_id: 1, qty: 350, stock: 350>, #<Availability id: nil, option_id: 1, qty: 350, stock: 350>, #<Availability id: nil, option_id: 1, qty: 350, stock: 350>, #<A...
irb(main):012:0> a[0].option_event_dt
=> nil

編輯 2

我通過執行“傳統”查詢得到了一個運行結果,lateral_joins inside joins:

module Catalog
  class AllBookingsAvailableQuery

    SELECT_SQL = <<~SQL
            "availabilities".id,
            "availabilities".timeslot_id,
            "availabilities".option_id,
            "variations".id as variation_id,
            "timeslots".begin_at,
            coalesce(locale_variation_trans.name, first_variation_trans.name) as variation_name,
            "options".id as option_id,
            coalesce(locale_option_trans.name, first_option_trans.name) as option_name,
            "options".price,
            "vats".value as option_vat,
            "options".min_sell_qty,
            "options".max_sell_qty,
            "options".sort_index,
            "availabilities".qty,
            "availabilities".stock
    SQL

    def self.call( user: , product:, locale: nil, begin_at: nil, end_at: nil)
      query = Catalog::AllBookingsAvailableQuery.new(  user: user, product: product, locale: locale, begin_at: begin_at, end_at: end_at )
      query.fetch_availabilities
    end

    def initialize( user: , product: ,  locale: , begin_at: Time.now, end_at: Time.now + 2.weeks)
      @locale_id   = locale.id
      user.switch_tenant!
      @user_id     = user.id
      @product_id  = product.id
      @begin_at    = begin_at
      @end_at      = end_at
    end

    def fetch_availabilities
      Availability.joins( :timeslot, option: [ :vat, :option_translations, variation: [:product, :variation_translations]]).
        joins( lat_join_names).
        select( SELECT_SQL ).
        where( options: { variations: { products: { id: @product_id } } }).
        where('"timeslots".begin_at BETWEEN ? AND ?', @begin_at, @end_at).
        order('variation_id, sort_index, begin_at')    end

    def lat_join_names
      <<~SQL
            LEFT JOIN LATERAL (
                SELECT vts1.name
                FROM "user_#{@user_id}".variation_translations vts1
                WHERE vts1.variation_id = "variations".id AND vts1.locale_id = #{@locale_id}
                LIMIT 1
            ) locale_variation_trans on TRUE
             LEFT JOIN LATERAL (
                 SELECT vts2.name
                 FROM "user_#{@user_id}".variation_translations vts2
                 WHERE vts2.variation_id = "variations".id
                 LIMIT 1
                 ) first_variation_trans on TRUE
            LEFT JOIN LATERAL (
                SELECT ots1.name
                FROM "user_#{@user_id}".option_translations ots1
                WHERE ots1.option_id = "options".id AND ots1.locale_id = #{@locale_id}
                LIMIT 1
            ) as locale_option_trans on TRUE
             LEFT JOIN LATERAL (
                    SELECT ots2.name
                    FROM "user_#{@user_id}".option_translations ots2
                    WHERE ots2.option_id = "options".id
                    LIMIT 1
                 ) as first_option_trans on TRUE
      SQL
    end

  end
end

好的,我看到了你的問題。 begin_atend_atnil 僅僅因為您設置了默認值並不意味着它們不能被nil覆蓋。 例如:

def foo(bar:'baz') 
  bar.inspect
end 
foo
#=> "/"baz/"" 
foo(bar:nil)
#=> "nil" 

您的問題出現在self.call中,它將begins_atends_at設置為默認值nil ,然后將這些值傳遞過去。

如果您想使用此功能,我會將initialize更改為:

 def initialize( user: , product: ,  locale: , begin_at: nil, end_at: nil)
      @locale_id   = locale.id
      user.switch_tenant!
      @user_id     = user.id
      @product_id  = product.id
      @begin_at    = begin_at || Time.now
      @end_at      = end_at || @begin_at + 3.months
    end

這樣,如果begin_atnil ,它將使用Time.now ,如果end_atnil ,則它將使用@begin_at + 3.months

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM