簡體   English   中英

Ruby on Rails。未初始化的常量

[英]Ruby on Rails. Uninitialized constant

這很奇怪,但是:

上傳器類(app / uploaders):

class ImageUploader < CarrierWave::Uploader::Base
  include CarrierWave::RMagick

  # ....

  version :thumb, from_version: :preview do
    process resize_to_limit: [Image::THUMB_WIDTH]
  end

圖像類(app / models):

class Image < ActiveRecord::Base
  include Rails.application.routes.url_helpers
  mount_uploader :image, ImageUploader

  THUMB_WIDTH = 220
  PREVIEW_WIDTH = 460
  MAX_WIDTH = 960

申請說:

uninitialized constant Image::THUMB_WIDTH

  version :thumb, from_version: :preview do
    process resize_to_limit: [Image::THUMB_WIDTH] #<<<----
  end

  version :preview, from_version: :fullsize do

怎么了?

更新:

阿吉斯指出了原因。

Bounty將在2天內應用此問題的最佳解決方案。 我不喜歡代碼分離,例如在初始化器中創建一個新類,用於保存Image類的所有常量。這個解決方案很糟糕,因為它帶來了不一致和代碼碎片。

一般答案

就rails自動加載器而言,你有一個雞或蛋的情況,在一般意義上解決這個問題的“軌道方式”是重構元代碼,以便類名作為字符串值而不是類傳遞,例如:

belongs_to :manager, class_name: "Employee"

在所有類都被加載的時候, belongs_to會在class_name上調用constantize ,這樣就可以避免雞和蛋的問題“軌道”。

@Stoic建議的內容本質上是繞過image.rbimage_uploader.rb加載時的評估主題的變體:

model.class.const_get("THUMB_WIDTH")

也可以被表述為:

'Image'.constantize.const_get("THUMB_WIDTH")

結果是一樣的,從中可以得出的一般教訓是: 避免在類加載時代碼中使用另一個類名文字,或者換句話說belongs_to :manager, class_name: "Employee"是好的, belongs_to :manager, class_name: Employee會很糟糕的。

它不漂亮,但它可能是避免這些頭痛的最優雅的通​​用方法


問題具體答案

查看問題的一種不同方式是縮略圖圖標寬度實際上是上傳者而不是模型的關注點,並且您實際上看到了一個不恰當的親密代碼氣味的邊緣情況( http://www.codinghorror。 com / blog / 2006/05 / code-smells.html )。

注意那些花費太多時間在一起的課程,或者以不恰當的方式進行交流的課程。 課程應該盡可能少地了解彼此。

所以,如果你是這個思想學派(我傾向於這個方向),解決方案是使THUMB_WIDTH成為ImageUploader類的常量,問題就會消失。

無論如何,將模型中的不同域關注點分離出來通常是一個好主意,因為模型會變得臃腫和無法管理 - 您可以將上傳器類視為模型的服務類,旨在以相同的方式提取特定的域問題值對象,表單對象等在http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/中處理


計划C將在你的application.rb和交叉手指切換config.autoload_paths位置:)

發生這種情況是因為ImageUploader類的代碼在Image類的代碼之前進行了評估。 因此,在評估Image::THUMB_WIDTH ,發生常量查找並搜索Image常量但由於尚未加載相關文件而找不到它。

您可以通過在ImageUploader文件的頂部添加此定義來解決此問題:

class Image; end

您也可以在初始化程序中執行相同操作(例如config/initializers/image.rb )。

這將確保Image類在引導過程開始時加載,在其他所有內容之前加載,如果您不希望在同一文件中定義不同類,則可以將其視為更清晰的解決方案。

我認為您應該將常量移出類,並在初始化程序中移動到更寬的命名空間中。 或者您可以在圖像類上創建一個方法來訪問常量。 這可能會也可能不會緩解您的問題。

class Image
  THUMB_WIDTH = 220

  def self.thumb_width
    THUMB_WIDTH
  end
end

process resize_to_limit: [Image.thumb_width]

Rails提供了存儲配置數據的便利位置: Rails.application.config 這將解決您的依賴性問題,並很好地將配置與邏輯分開。 我知道你說你不想把它們分開,但我覺得這很干凈(比創建一個常量模塊更好)。

在config / application.rb中:

config.image_sizes = {
  thumb_width: 220,
  preview_width: 460,
  max_width: 960,
}.with_indifferent_access

在app / uploaders / image_uploader.rb中:

version :thumb, from_version: :preview do
  process resize_to_limit: [Rails.application.config.image_sizes[:thumb_width]]
end

在定義THUMB_WIDTH之前,您的ImageUploader類正在自動加載。 更改定義順序:

class Image < ActiveRecord::Base
  include Rails.application.routes.url_helpers

  THUMB_WIDTH = 220
  PREVIEW_WIDTH = 460
  MAX_WIDTH = 960

  mount_uploader :image, ImageUploader

end

我不能確定問題根,但可以假設它與文件加載順序有關。 加載文件時評估的class范圍內容。 並命名Image::THUMB_WIDTH在它被定義(和文件加載)之前使用。

但仍不確定,因為收到的消息不是uninitialized constant Image ,而是uninitialized constant Image::THUMB_WIDTH 它可以涉及一些項目細節和結構,這在當前問題上下文中沒有描述。

使用const_get解決方案可以命名為hack(dirty,nasty hack),因為你不確定它是否在當前情況下的加載時刻進行了初始化。

我會說這段代碼有設計問題,導致類共享責任,並且在代碼評估時需要彼此。 也許您應該將此依賴關系移動到初始化時間,例如 - 在調用new時將預期的圖像參數傳遞給ImageUploader 或者更好地從Image完全移動它,因為自動生成的拇指大小幾乎不是Image抽象的一部分。 它取決於Image類的目的,你應該首先澄清它。

記住,單一對象 - 單一責任。 這種方式將以最通用和可控的方式解決依賴問題。

ImageUploader頂部添加require Image無濟於事?

不確定,因為Image需要ImageUploader ,所以你可能在那里有一個循環引用。

因此,如果這不起作用,我會將圖像配置提取到另一個類/模塊。 你說它屬於Image但是imho它更多是應用程序配置,對吧? 它指定了如何在此應用程序中顯示/存儲圖像。 如果要在其他應用程序中顯示圖像,則可能會更改。

所以,要么添加app/models/image/configuration.rb

class Image
  module Configuration
    THUMB_WIDTH = 220
    PREVIEW_WIDTH = 460
    MAX_WIDTH = 960
  end
end

然后在你的ImageUploader你可以寫

require `image/configuration`

並使用Image::Configuration::THUMB_WIDTH

我通常使用這樣的配置屬性:我將它們放在config.yml ,我將其加載到初始化程序中。 使用類似的東西對自己來說真的很容易

appl_config_file = "#{Rails.root}/config/config.yml"
raw_config = File.read(appl_config_file)
APP_CONFIG = HashWithIndifferentAccess.new(YAML.load(raw_config)["#{Rails.env}"])

或使用像rails_config這樣的gem

請記住評估順序在動態語言中非常重要這一事實,最好始終以一種方式構建代碼,以保護您免受此類問題的影響。 考慮以下列方式構建代碼:

class Example
  extend Extensions
  include Inclusions
  CONSTANTS = "Should Consistently Follow"

  attr_accessor :notebook
  attr_reader :book
  attr_writer :note

  then_other_available :macro_like_methods

  def initialize; end

  def self.class_methods; end

  def instance_methods; end

  protected
    # ...
  private
    # ...
end

看看這個RailsCasts劇集的來源,我認為你應該使用:

process resize_to_limit: [model.class.const_get("THUMB_WIDTH")]

這種方法的好處是你可以在任何其他模型中簡單地聲明一個THUMB_WIDTH常量,然后簡單地使用這個上傳器類來處理該模型,以及:)

暫無
暫無

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

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