Ruby on Rails: 全局常量应该在哪里定义?

237
我刚刚开始使用我的第一个Ruby on Rails Web应用程序。我有许多不同的模型、视图、控制器等等。
我想找到一个好的地方来存放真正全局常量的定义,这些常量适用于整个应用程序。特别是,它们适用于我的模型逻辑和视图决策。我找不到任何DRY的地方来放置这些定义,使它们在所有我的模型和所有我的视图中都可用。
以具体的例子为例,我想要一个常量COLOURS = ['white', 'blue', 'black', 'red', 'green']。这在很多地方都被使用,包括模型和视图。我应该把它定义在哪里,只需要在一个地方就可以访问?
我尝试过以下方法: - 在与它们最相关的model.rb文件中使用常量类变量,例如@@COLOURS = [...]。但我找不到一个合理的方法来定义它,以便我可以在我的视图中写Card.COLOURS而不是像Card.first.COLOURS这样的笨拙方式。 - 在模型上使用方法,例如def colours ['white',...] end - 同样的问题。 - 在application_helper.rb中使用方法 - 这是我目前正在做的事情,但是helpers只能在视图中使用,不能在模型中使用。 - 我认为我可能在application.rb或environment.rb中尝试过某些东西,但那些似乎不是正确的方法(而且它们似乎也不起作用)。
难道没有一种方法可以定义任何可以从模型和视图中访问的内容吗?我的意思是,我知道模型和视图应该是分开的,但在某些领域,它们肯定需要引用相同的领域特定知识吧?

我很感激这篇文章虽然有点晚,但对于其他读者来说,我想知道为什么你不直接在模型中定义它们,并使用控制器将它们传递给视图。通过这种方式,您将拥有更清晰的关注点分离 - 而不是创建控制器/视图和模型/视图之间的依赖关系。 - Tom Tom
2
@TomTom:将这些常量传递到需要它们的每个视图和帮助程序中?换句话说,让控制器知道哪些视图需要哪些常量?这听起来更像是MVC的违规行为。 - AlexC
13个回答

259
如果你的模型真正“负责”这些常量,那么应该将它们存储在其中。你可以创建类方法来访问它们,而无需创建新的对象实例:
如果您的模型确实“负责”这些常量,则应将其放置其中。您可以创建类方法来访问它们,而不需要创建一个新的对象实例:
class Card < ActiveRecord::Base
  def self.colours
    ['white', 'blue']
  end
end

# accessible like this
Card.colours

你还可以创建类变量和访问器。但是这种方法不被推荐,因为在继承和多线程环境中,类变量可能会表现出意料之外的行为。

class Card < ActiveRecord::Base
  @@colours = ['white', 'blue'].freeze
  cattr_reader :colours
end

# accessible the same as above
Card.colours

上述两个选项允许您在每次访问器方法调用时更改返回的数组(如果需要)。如果您有一个真正不可更改的常量,您也可以在模型类上定义它:

class Card < ActiveRecord::Base
  COLOURS = ['white', 'blue'].freeze
end

# accessible as
Card::COLOURS

你也可以像下面的例子一样,在初始化器中创建全局常量,这些常量可以从任何地方访问。如果你的颜色确实是全局的,并且在多个模型上下文中使用,那么这可能是最好的地方。

# put this into config/initializers/my_constants.rb
COLOURS = ['white', 'blue'].freeze

# accessible as a top-level constant this time
COLOURS

注意:当我们定义常量时,通常希望将数组freeze。这可以防止其他代码在之后(无意间)修改该数组,例如添加新元素。一旦对象被冻结,就不能再更改它。


2
非常感谢。看起来我缺少了定义类方法的 Ruby 类技巧。但是在这种情况下,我实际上喜欢初始化选项,因为颜色在多个模型和视图中使用。非常感谢! - AlexC
26
如果选择使用 config/initializers/my_constants.rb 这个路径,请记得重新启动服务器:touch tmp/restart.txt - user664833
4
“def self.colours”这个例子并不理想。每次调用“def self.colours”时,“都会返回一个新的数组实例”。在这种情况下,“#freeze”也无法解决问题。最佳做法是将其声明为Ruby常量,这样您将始终获得相同的对象。 - Zabba
放在 config/initializers/my_constants.rb 中的常量在视图模板中无法访问,但是 Card::COLOURS 可以:uninitialized constant ActionView::CompiledTemplates::COLOURS - prograils
@prograils 在初始化程序中添加常量后,您是否重新启动了服务器? - Kelvin
显示剩余2条评论

71

一些选项:

使用常量:

class Card
  COLOURS = ['white', 'blue', 'black', 'red', 'green', 'yellow'].freeze
end

使用类实例变量进行延迟加载:

class Card
  def self.colours
    @colours ||= ['white', 'blue', 'black', 'red', 'green', 'yellow'].freeze
  end
end

如果这是一个真正的全局常量(尽量避免这种类型的全局常量),你也可以考虑把它放在config/initializers/my_constants.rb这样的顶级常量中。


1
嘿。公平的评论 - 当我从记忆中输入我的示例时出现了语法错误 :) 感谢您的提示! - AlexC
2
然后在类中使用extend方法扩展模块,这样就可以通过Card.COLOURS来访问。 - undefinedvariable
当我使用 extend 时它对我不起作用。 当我使用 include 时,我可以像这样访问: Card :: COLOURS - Abhi
你绝对不应该把它放在/models下面。如果你创建一个初始化程序会更好。 - linkyndy
@linkyndy 我认为把它放在/models下是可以的,但前提是它必须在一个模块内,例如在名为models/constants.rb的文件中定义一个模块Constants; COLOURS = ...; end - Kelvin
我会把它放在 ~/lib/constants 下面。 - Peter Bloom

67
截至Rails 4.2版本,您可以使用config.x属性:
# config/application.rb (or config/custom.rb if you prefer)
config.x.colours.options = %w[white blue black red green]
config.x.colours.default = 'white'

将作为可用的选项:

Rails.configuration.x.colours.options
# => ["white", "blue", "black", "red", "green"]
Rails.configuration.x.colours.default
# => "white"

另一种加载自定义配置的方法:

# config/colours.yml
default: &default
  options:
    - white
    - blue
    - black
    - red
    - green
  default: white
development:
  *default
production:
  *default

# config/application.rb
config.colours = config_for(:colours)

Rails.configuration.colours
# => {"options"=>["white", "blue", "black", "red", "green"], "default"=>"white"}
Rails.configuration.colours['default']
# => "white"

在Rails 5, 67中,您可以直接使用configuration对象进行自定义配置,除了config.x之外。但是,它只能用于非嵌套配置:
# config/application.rb
config.colours = %w[white blue black red green]

它将作为以下内容提供:
Rails.configuration.colours
# => ["white", "blue", "black", "red", "green"]

Rails 6.1引入了“共享”组,可简化YML配置:
# config/colours.yml
shared:
  options:
    - white
    - blue
    - black
    - red
    - green
  default: white

2
我最喜欢 Rails.configuration.colors (虽然希望它不要太长)。 - Tom Rossi
@TomRossi 我同意,例如 configconfiguration 一样好。我们可能希望在某个时候得到一个快捷方式 :) - Halil Özgür
这仍然是在Rails 6中定义常量以在多个控制器之间共享的最佳方法吗?感谢您的回答! - Crashalot
@Crashalot,它仍然在文档中列出。"最好的"?这取决于情况。它可以在它们的共同祖先中。或者如果其他东西之间没有中间层,则可以在“ApplicationController”中。如果常量与控制器没有直接关联,我仍然会考虑全局配置等。 - Halil Özgür
@HalilÖzgür,感谢您的回复。在共同祖先中如何定义常量? - Crashalot
@Crashalot 假设这些多个控制器都继承自 ApiController,即 FooController < ApiControllerBarController < ApiController 等。然后您可以在 ApiController 中定义常量,例如:https://gist.github.com/halilim/b1e3c6a8d93152e3fde6dad7266f1cd3 - Halil Özgür

26
如果一个常量需要在多个类中使用,我会将它放在config/initializers/constant.rb文件中,始终使用全大写(下面是状态列表的截断部分) 。
STATES = ['AK', 'AL', ... 'WI', 'WV', 'WY']

除了在模型代码中,它们可以在整个应用程序中使用,如下所示:

它们可以在整个应用程序中使用,除了在模型代码中。

    <%= form.label :states, %>
    <%= form.select :states, STATES, {} %>

要在模型中使用常量,请使用attr_accessor使常量可用。

class Customer < ActiveRecord::Base
    attr_accessor :STATES

    validates :state, inclusion: {in: STATES, message: "-- choose a State from the drop down list."}
end

1
很好,但是config/initializers/constants.rb可能会是更好的选择。 - Adit Saxena
我也使用这个,但最近遇到了一个问题,即这些常量在application.rb中无法访问。 - Zia Ul Rehman Mughal
我的常量一开始是可以用的,但因为某些原因停止了(可能是因为我的文件移出了初始化器)。在查看了这个答案后,我仔细检查了一下并把它们移回去了,现在又可以用了。 - Muhammad Nasir Shamshad
我认为不需要使用attr_accessor。你是在谈论特定的Rails版本吗? - Mayuresh Srivastava

16

对于应用程序范围的设置和全局常量,我建议使用Settingslogic。这些设置存储在YML文件中,并可以从模型、视图和控制器中访问。此外,您可以为所有环境创建不同的设置:

  # app/config/application.yml
  defaults: &defaults
    cool:
      sweet: nested settings
    neat_setting: 24
    awesome_setting: <%= "Did you know 5 + 5 = #{5 + 5}?" %>

    colors: "white blue black red green"

  development:
    <<: *defaults
    neat_setting: 800

  test:
    <<: *defaults

  production:
    <<: *defaults

在视图中的某个地方(我更喜欢使用帮助方法来处理这种情况),或者在模型中,你可以获取颜色数组,例如:Settings.colors.split(/\s/)。它非常灵活。而且你不需要重新发明轮子。


10

尝试将所有常量放在一个地方。在我的应用程序中,我已经创建了一个常量文件夹,并将其放置在初始化器文件夹中,如下所示:

enter image description here

在这些文件中,我通常会保留所有的常量。
在你的情况下,你可以在constants文件夹下创建文件colors_constant.rb

colors_constant.rb

enter image description here

不要忘记重新启动服务器


6

使用类方法:

def self.colours
  ['white', 'red', 'black']
end

然后Model.colours将返回该数组。或者,创建一个初始化器并在模块中封装常量以避免命名空间冲突。


5

如果您想将常量定义在一个地方,另一种选择是:

module DSL
  module Constants
    MY_CONSTANT = 1
  end
end

但仍然使它们在全局范围内可见,而不必以完全限定的方式访问它们:

DSL::Constants::MY_CONSTANT # => 1
MY_CONSTANT # => NameError: uninitialized constant MY_CONSTANT
Object.instance_eval { include DSL::Constants }
MY_CONSTANT # => 1

3

将应用程序范围内的全局常量放置在 config/application 中是一种常见做法。

module MyApp
  FOO ||= ENV.fetch('FOO', nil)
  BAR ||= %w(one two three)

  class Application < Rails::Application
    config.foo_bar = :baz
  end
end

1
我通常在我的Rails程序中有一个“查找”模型/表,并将其用于常量。如果常量在不同的环境下会有所不同,则非常有用。此外,如果您计划扩展它们,例如您想在以后添加“黄色”,则可以简单地向查找表中添加一行并完成操作。
如果您授予管理员权限来修改此表,则他们不会寻求您的维护。 :) DRY.
这是我的迁移代码的样子:
class CreateLookups < ActiveRecord::Migration
  def change
    create_table :lookups do |t|
      t.string :group_key
      t.string :lookup_key
      t.string :lookup_value
      t.timestamps
    end
  end
end

我使用 seeds.rb 来预先填充它。
Lookup.find_or_create_by_group_key_and_lookup_key_and_lookup_value!(group_key: 'development_COLORS', lookup_key: 'color1', lookup_value: 'red');

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接