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


213

我刚开始使用我的第一个Ruby on Rails Webapp。我有很多不同的模型,视图,控制器等等。

我想找到一个粘贴真正的全局常量的好地方,该定义适用于我的整个应用程序。特别是,它们既适用于我模型的逻辑,也适用于我观点中的决策。我找不到任何DRY放置这些定义的位置,这些定义对于我的所有模型和我的所有视图均可用。

举一个具体的例子,我想要一个常量COLOURS = ['white', 'blue', 'black', 'red', 'green']。在模型和视图中,都可以在各处使用它。我在哪里可以在一个地方定义它以便访问?

我尝试过的

  • 与它们最相关的model.rb文件中的常量类变量,例如@@COLOURS = [...]。但是我找不到一种明智的定义方式,这样我就可以在我的观点中写,Card.COLOURS而不是像这样的笨拙了Card.first.COLOURS
  • 模型上的方法,类似def colours ['white',...] end-同样的问题。
  • application_helper.rb中的一个方法-到目前为止,这是我正在做的事情,但是只能在视图中访问助手,而不能在模型中访问
  • 我想我可能已经在application.rb或environment.rb中尝试了一些东西,但是那些看起来似乎并不正确(它们似乎也不起作用)

难道没有办法定义从模型和视图都可以访问的内容吗?我的意思是,我知道模型和视图应该分开,但是肯定在某些领域中有时会需要引用相同的领域特定知识吗?



我很欣赏这确实很晚,但是对于其他读者,我想知道为什么您不只是在模型中定义它们,而是使用控制器将它们传递给视图。这样,您就可以将关注点更清晰地分开-而不是在控制器/视图与模型/视图之间创建依赖关系。
Tom Tom

2
@TomTom:将这些常量传递给需要它们的每个视图和帮助器?换句话说,使控制器知道哪些视图需要哪些常量?听起来更像是违反了MVC。
AlexC

Answers:


228

如果您的模型对常量确实是“负责任的”,则应将其放在此处。您可以创建类方法来访问它们,而无需创建新的对象实例:

class Card < ActiveRecord::Base
  def self.colours
    ['white', 'blue']
  end
end

# accessible like this
Card.colours

另外,您可以创建类变量和访问器。但是不建议这样做,因为类变量在继承和多线程环境中可能会表现出令人惊讶的效果。

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

# accessible the same as above

如果需要,上面的两个选项使您可以在每次访问accessor方法时更改返回的数组。如果您有一个真正不变的常数,也可以在模型类上定义它:

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

注意:当我们在上面定义常量时,通常我们想要freeze数组。这样可以防止其他代码稍后(无意间)通过例如添加新元素来修改数组。对象冻结后,就无法再更改了。


1
非常感谢你。似乎我缺少定义类方法的Ruby class-fu。但实际上,在这种情况下,我喜欢初始化器选项,因为在多个模型和视图中使用了颜色。非常感谢!
AlexC 2010年

21
如果要config/initializers/my_constants.rb沿着路线行驶,请记住重新启动服务器:touch tmp/restart.txt
user664833

4
这个def self.colours例子并不理想。每次调用时def self.colours将返回该数组的新实例#freeze在这种情况下将无济于事。最佳实践是将其声明为Ruby常量,在这种情况下,您将始终返回同一对象。
扎巴

@Zabba如果单个数组的分配给您的应用带来了显着的变化,那么您可能首先不应该使用Ruby ...也就是说,使用一个方法并每次返回一个全新的数组可能会有几个优点:(1)这是您在Ruby中可以很接近类边界上的不可变对象的方法;(2)您可以在类上保持统一的接口,并可以稍后根据固有状态修改返回值(例如从数据库读取颜色),而无需更改界面。
霍尔格

@Holger只是,至少可以使用常量来实现您的目标之一:class Card; COLOURS = ['white', 'blue'].freeze; def self.colours; COLOURS; end; end也就是说,以任何一种语言分配数组都可能会出现问题。一方面,它没有(好的)理由使用内存。如果从数据库加载,并且想缓存该值,则还可以使用类实例变量,该类实例变量可以通过使用该def self.colours方法进行延迟加载。虽然同意不变性。
扎巴

70

一些选项:

使用常量:

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 2010年

2
然后extend是该类中的模块,因此可用于Card.COLOURS
undefinedvariable

使用extend它对我不起作用时。使用时,include我可以访问:Card::COLOURS
Abhi

您绝对不应将它放在下/models。如果创建一个初始化器,那就更好了。
linkyndy 2015年

@linkyndy我可以说可以放下它/models,但前提是它必须位于模块内部,例如module Constants; COLOURS = ...; end在名为的文件中models/constants.rb
开尔文

56

从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 56中configuration除了之外,您还可以直接将该对象用于自定义配置config.x。但是,它只能用于非嵌套配置:

# config/application.rb
config.colours = %w[white blue black red green]

它可以作为:

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

1
Rails.configuration.colours最喜欢(尽管我希望不会太久)
Tom Rossi

@TomRossi我同意,例如config和一样好configuration。我们可能希望在某个时候走捷径:)
HalilÖzgür19

这仍然是Rails 6中定义要在多个控制器之间共享的常数的最佳方法吗?感谢你的回答!
Crashalot

18

如果一个以上的类中需要一个常量,我总是以大写形式将其放在config / initializers / contant.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中访问
齐亚。拉赫曼莫卧儿

我的常数正常工作,但由于某种原因停止了(因为我的文件从初始化程序中移出)。检查完此答案后,我仔细查看并移回它们,然后开始工作。谢谢
穆罕默德·纳西尔·沙姆沙德19'Mar

我认为不需要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/)。非常灵活。而且您不需要发明自行车。


7

使用类方法:

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

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


4

尝试将所有常量保持在一个位置,在我的应用程序中,我在初始化器中创建了constants文件夹,如下所示:

在此处输入图片说明

而且我通常在这些文件中保持所有不变。

在您的情况下,您可以在常量文件夹下创建文件,如下所示: colors_constant.rb

colors_constant.rb

在此处输入图片说明

不要忘记重启服务器


1
这是我在这里找到的最佳答案。谢谢。
Promise Preston

3

如果要在一个位置定义常量,则可以使用另一种选择:

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

2

我通常在rails程序中有一个“查找”模型/表,并将其用于常量。如果常量在不同的环境下会有所不同,这将非常有用。另外,如果您有扩展它们的计划,例如您想在以后添加“ yellow”,则只需在查找表中添加一个新行即可。

如果您授予管理员修改该表的权限,他们将不会来维护您。:) 干燥。

我的迁移代码如下所示:

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');

1

全局变量应在config/initializers目录中声明

COLOURS = %w(white blue black red green)

谢谢!其他人已经提到了这一点。这是Holger回答的最后一行,尽管Zabba对此提出警告,但Zabba也提到了此技术。
AlexC

0

根据您的情况,您还可以定义一些环境变量,并通过ENV['some-var']ruby代码通过获取它,该解决方案可能不适合您,但希望对您有所帮助。

例如:你可以创建不同的文件.development_env.production_env.test_env并根据您的应用程序环境中加载它,看看这个根dotenv护栏它为您自动执行此。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.