【问题标题】:Using the default value defined in model in a query in a Rails application在 Rails 应用程序的查询中使用模型中定义的默认值
【发布时间】:2019-11-17 19:14:04
【问题描述】:

我正在使用 Rails 5。我有一个 Product 模型定义如下

class Product < ActiveRecord::Base
  validates :image_url, presence: true

  def image_url
    super || 'http://someplaceholderimage.com'
  end
end

每个产品都有一个 image_url 属性。现在,当我运行以下查询时:

Product.all.where.not(image_url: nil)

我希望它返回所有产品,即使数据库中的 image_url 对它们中的任何一个都为零。但是我在模型中定义的默认值没有被使用,我只得到那些 image_url 列在数据库中具有非 nil 值的产品。我该如何解决这个问题?

谢谢

【问题讨论】:

    标签: ruby-on-rails activerecord ruby-on-rails-5


    【解决方案1】:

    这是一个实例方法。它适用于 Product 类的任何实例。

    通过模型对数据库进行的每个查询都是一个类方法。所以它们不会像你预期的那样工作。

    您可以做的是为数据库中的该列添加一个默认值。因此,每当插入一条记录时,使用 nil (NULL) image_url,它将采用您定义的默认值。


    还可以使用 SQL CASE 表达式添加默认值,从而根据您的需求增加更多灵活性:

    Product.select("CASE WHEN image_url IS NULL
                    THEN 'http://someplaceholderimage.com'
                    ELSE image_url END, *")
    

    (这是 PostgreSQL 语法)

    结合这两种方式,您无需向模型添加更多代码。

    要更改列的默认值,可以使用change_column_default

    class ChangeImageUrlDefaultProducts < ActiveRecord::Migration[6.0]
      def change
        change_column_default :products, :image_url, from: nil, to: 'http://someplaceholderimage.com'
      end
    end
    

    通过使用fromto 选项参数,这是可逆的。

    【讨论】:

    • 感谢您的回复。但是这种方法仅适用于我创建的新记录。任何具有 nil 值的过去记录都不会获得默认值。有没有办法让它适用于过去的记录,其中 image_url 的值也为零?
    • 您可以通过一个查询来更新所有这些。 Product.where(image_url: nil).update_all(image_url: 'http://someplaceholderimage.com')。关于你的问题;这取决于您要查询数据库的内容,我可以看到您正在尝试获取所有产品,无论 image_url 是否为 nil,因为 Product.all 就足够了。
    【解决方案2】:

    在 Rails 中.where() 正在构建一些要在数据库上调用的 SQL。所以代码正在按照你的要求做:它会进入数据库并询问 image_url 不是 nil 的所有记录。

    您在模型上定义的方法是:

    1. 在数据库中查找 image_url

    2. 如果有则返回

    3. 如果没有则返回 'http://someplaceholderimage.com'

    为了实现您的既定目标:

    我希望它返回所有产品,即使数据库中的 image_url 为 nil

    您只需拨打Product.all

    如果您不希望这是一个条件,我不确定您为什么要添加 .where.not()

    真正把要点带回家:期望数据库返回 :image_url 为 nil 或非 nil 的记录:

    Product.where(image_url: nil).or(Product.where.not(image_url: nil)) 等同于调用Product.all


    如果你想确保每条记录都有一个在数据库中定义的 URL,你可以在模型中做一个 before_validation 回调:

    class Product < ActiveRecord::Base
      validates :image_url, presence: true
    
      before_save :default_image_url, if: ->{ image_url.blank? }
    
      # def image_url
      #   super || 'http://someplaceholderimage.com'
      # end
    
      private
    
      def default_image_url
        image_url ||= 'http://someplaceholderimage.com'
      end
    end
    

    如果您想知道哪些记录返回默认的:image_url,哪些记录在数据库中保存了:image_url,则需要重命名该方法,使其不会与数据库列名冲突:

    class Product < ActiveRecord::Base
      validates :image_url, presence: true
    
      def image
        image_url || 'http://someplaceholderimage.com'
      end
    end
    

    现在调用 @product.image_url 将返回数据库中的任何内容,调用 @product.image 将返回 dB 值或占位符值。


    但是,还有另一个问题: 您的validates :image_url, presence: true 表示如果:image_url 为空白,则该记录将永远不会保存。

    因此,当您已经确保每条记录都不能有 nil 值时,让数据库查找潜在的 nil 值有点让人头疼。


    终切线: 如果你必须让方法名和数据库列名匹配,我认为super不是一个好的选择。

    我认为你应该使用这个:

    def image_url
      read_attribute(:image_url) || 'http://someplaceholderimage.com'
    end
    

    如果没有别的,它比super 更清楚发生了什么。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-05-15
      • 2017-06-24
      • 1970-01-01
      相关资源
      最近更新 更多