【问题标题】:Ruby on Rails: Where to define global constants?Ruby on Rails:在哪里定义全局常量?
【发布时间】:2011-05-05 21:10:24
【问题描述】:

我刚刚开始使用我的第一个 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 中的一个方法 - 这是我目前正在做的,但助手只能在视图中访问,而不能在模型中访问
  • 我想我可能在 application.rb 或 environment.rb 中尝试过一些东西,但它们看起来并不正确(而且它们似乎也不起作用)

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

【问题讨论】:

  • 我很感激这真的太晚了,但对于其他读者,我想知道为什么你不只是在你的模型中定义它们并使用你的控制器将它们传递给你的视图。通过这种方式,您可以更清晰地分离关注点 - 而不是在控制器/视图和模型/视图之间创建依赖关系。
  • @TomTom:将这些常量传递给需要它们的每个视图和助手?换句话说,让控制器知道哪些视图需要哪些常量?这听起来更像是对 MVC 的违反。

标签: ruby-on-rails constants global


【解决方案1】:

使用类方法:

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

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

【讨论】:

    【解决方案2】:

    如果您的模型真的对常量“负责”,您应该将它们粘贴在那里。您可以创建类方法来访问它们,而无需创建新的对象实例:

    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 数组。这可以防止其他代码稍后(无意中)通过例如修改数组添加一个新元素。一旦对象被冻结,就无法再更改。

    【讨论】:

    • 非常感谢。看起来我错过了定义类方法的 Ruby class-fu。但我实际上喜欢这种情况下的初始化选项,因为颜色用于多个模型和视图中。非常感谢!
    • 如果走config/initializers/my_constants.rb路线,记得重启服务器:touch tmp/restart.txt
    • def self.colours 示例并不理想。每次调用def self.colours将返回一个新的数组实例#freeze 在这种情况下无济于事。最佳做法是将其声明为 Ruby 常量,在这种情况下,您将始终返回相同的对象。
    • @Zabba 如果单个数组的分配对您的应用程序产生了显着影响,那么您可能一开始就不应该使用 Ruby ......也就是说,使用一个方法并完全返回一个每次新数组都有几个优点:(1)它是最接近 Ruby 中类边界上的不可变对象的东西;(2)您在类上保持统一接口,并有可能调整返回值稍后基于固有状态(例如通过从数据库中读取颜色)而不更改界面。
    • @Holger Just,至少您的一个目标仍然可以使用常量来实现:class Card; COLOURS = ['white', 'blue'].freeze; def self.colours; COLOURS; end; end 也就是说,以任何语言分配数组都可能存在问题;一方面,它没有(好的)理由使用内存。如果从数据库加载,并且想要缓存值,也可以使用类实例变量,可以使用def self.colours方法延迟加载。不过同意不变性方面。
    【解决方案3】:

    一些选项:

    使用常量:

    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 中的顶级常量。

    【讨论】:

    • 嘿。公平评论 - 我的示例从内存输入时出现语法错误 :) 感谢您的提示!
    • 然后 extend 类中的模块,这样它就可以通过 Card.COLOURS 使用。
    • 当使用extend 时,它对我不起作用。使用include 时,我可以访问:Card::COLOURS
    • 你绝对不应该把它放在/models下面。如果你创建一个初始化器会好很多。
    • @linkyndy 我想说把它放在/models 下是可以的,但前提是它在一个模块中,例如module Constants; COLOURS = ...; end 在名为 models/constants.rb 的文件中。
    【解决方案4】:

    对于应用程序范围的设置和全局常量,我建议使用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/)。它非常灵活。而且你不需要发明自行车。

    【讨论】:

      【解决方案5】:

      如果在多个类中需要一个常量,我将它放在 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
      

      【讨论】:

      • 不错,config/initializers/constants.rb 可能会是更好的选择
      • 我也用这个,但最近遇到了在application.rb中无法访问这些常量的问题
      • 我的常量正在工作,但由于某种原因停止了(因为不知何故我的文件移出了初始化程序)。在检查了这个答案后,我仔细观察并将它们移回现在工作。谢谢
      • 我认为不需要 attr_accessor。您是在谈论任何特定的 Rails 版本吗?
      【解决方案6】:

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

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

      它将提供为:

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

      【讨论】:

      • 我最喜欢Rails.configuration.colours(虽然我希望它不要那么长)
      • @TomRossi 我同意,例如configconfiguration 一样好。我们可能希望在某个时候获得一条捷径:)
      • 这仍然是 Rails 6 中定义要在多个控制器之间共享的常量的最佳方式吗?谢谢你的回答!
      • @Crashalot 它仍然在文档中列出。 “最好的”?这取决于。它可以在他们共同的祖先。或者在ApplicationController 中,如果中间没有其他内容。如果常量与控制器没有直接关系,我仍然会考虑全局配置等。
      • @HalilÖzgür 感谢您的回复。如何定义共同祖先中的常量?
      【解决方案7】:

      我通常在我的 rails 程序中有一个“查找”模型/表,并将其用于常量。如果常数在不同的环境中有所不同,这将非常有用。此外,如果您有扩展它们的计划,假设您想在以后添加“黄色”,您可以简单地在查找表中添加一个新行并完成它。

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

      我的迁移代码如下所示:

      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
      

      我使用 seed.rb 来预填充它。

      Lookup.find_or_create_by_group_key_and_lookup_key_and_lookup_value!(group_key: 'development_COLORS', lookup_key: 'color1', lookup_value: 'red');
      

      【讨论】:

        【解决方案8】:

        另一种选择,如果你想在一个地方定义你的常量:

        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
        

        【讨论】:

          【解决方案9】:

          放置应用程序范围的全局常量的常见位置是config/application

          module MyApp
            FOO ||= ENV.fetch('FOO', nil)
            BAR ||= %w(one two three)
          
            class Application < Rails::Application
              config.foo_bar = :baz
            end
          end
          

          【讨论】:

            【解决方案10】:

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

            COLOURS = %w(white blue black red green)
            

            【讨论】:

            • 谢谢!其他人已经提到了这一点。这是 Holger 回答的最后一行,Zabba 也提到了这种技术,尽管 Zabba 警告不要这样做。
            【解决方案11】:

            根据你的情况,你也可以定义一些环境变量,并在ruby代码中通过ENV['some-var']获取,这个解决方案可能不适合你,但我希望它可以帮助别人。

            示例:您可以创建不同的文件 .development_env.production_env.test_env 并根据您的应用程序环境加载它,检查此生成 dotenv-rails 它会为您自动执行此操作。

            【讨论】:

              【解决方案12】:

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

              我通常在这些文件中保持不变。

              在您的情况下,您可以在常量文件夹下创建文件为colors_constant.rb

              colors_constant.rb

              别忘了重启服务器

              【讨论】:

              • 这是我在这里找到的最佳答案。谢谢。
              【解决方案13】:

              我认为你可以使用 gem config

              https://github.com/rubyconfig/config

              轻松处理和编辑

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2010-11-09
                • 2014-01-31
                • 2023-03-02
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多