Reputation: 3710
I'm just getting started with my first Ruby on Rails webapp. I've got a bunch of different models, views, controllers, and so on.
I'm wanting to find a good place to stick definitions of truly global constants, that apply across my whole app. In particular, they apply both in the logic of my models, and in the decisions taken in my views. I cannot find any DRY place to put these definitions where they're available both to all my models and also in all my views.
To take a specific example, I want a constant COLOURS = ['white', 'blue', 'black', 'red', 'green']
. This is used all over the place, in both models and views. Where can I define it in just one place so that it's accessible?
What I've tried:
@@COLOURS = [...]
. But I couldn't find a sane way to define it so that I can write in my views Card.COLOURS
rather than something kludgy like Card.first.COLOURS
.def colours ['white',...] end
- same problem.Is there just no way to define anything to be accessible both from models and from views? I mean, I know models and views should be separate, but surely in some domains there'll be times they need to refer to the same domain-specific knowledge?
Upvotes: 242
Views: 168636
Reputation: 15947
As of Rails 4.2, you can use the config.x
property:
# 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'
Which will be available as:
Rails.configuration.x.colours.options
# => ["white", "blue", "black", "red", "green"]
Rails.configuration.x.colours.default
# => "white"
Another method of loading custom config:
# 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"
In Rails 5, 6, and 7 you can use the configuration
object directly for custom configuration, in addition to config.x
. However, it can only be used for non-nested configuration:
# config/application.rb
config.colours = %w[white blue black red green]
It will be available as:
Rails.configuration.colours
# => ["white", "blue", "black", "red", "green"]
Rails 6.1 introduced shared
groups which can simplify YML configs:
# config/colours.yml
shared:
options:
- white
- blue
- black
- red
- green
default: white
Upvotes: 70
Reputation: 281
I think you can use gem config
https://github.com/rubyconfig/config
Easy handle and edit
Upvotes: 0
Reputation: 534
If a constant is needed in more than one class, I put it in config/initializers/constant.rb always in all caps (list of states below is truncated).
STATES = ['AK', 'AL', ... 'WI', 'WV', 'WY']
They are available through out the application except in model code as such:
<%= form.label :states, %>
<%= form.select :states, STATES, {} %>
To use the constant in a model, use attr_accessor to make the constant available.
class Customer < ActiveRecord::Base
attr_accessor :STATES
validates :state, inclusion: {in: STATES, message: "-- choose a State from the drop down list."}
end
Upvotes: 26
Reputation: 55718
If your model is really "responsible" for the constants you should stick them there. You can create class methods to access them without creating a new object instance:
class Card < ActiveRecord::Base
def self.colours
['white', 'blue']
end
end
# accessible like this
Card.colours
Alternatively, you can create class variables and an accessor. This is however discouraged as class variables might act kind of surprising with inheritance and in multi-thread environments.
class Card < ActiveRecord::Base
@@colours = ['white', 'blue'].freeze
cattr_reader :colours
end
# accessible the same as above
Card.colours
The two options above allow you to change the returned array on each invocation of the accessor method if required. If you have true a truly unchangeable constant, you can also define it on the model class:
class Card < ActiveRecord::Base
COLOURS = ['white', 'blue'].freeze
end
# accessible as
Card::COLOURS
You could also create global constants which are accessible from everywhere in an initializer like in the following example. This is probably the best place, if your colours are really global and used in more than one model context.
# put this into config/initializers/my_constants.rb
COLOURS = ['white', 'blue'].freeze
# accessible as a top-level constant this time
COLOURS
Note: when we define constants above, often we want to freeze
the array. That prevents other code from later (inadvertently) modifying the array by e.g. adding a new element. Once an object is frozen, it can't be changed anymore.
Upvotes: 262
Reputation: 12868
For application-wide settings and for global constants I recommend to use Settingslogic. This settings are stored in YML file and can be accessed from models, views and controllers. Furthermore, you can create different settings for all your environments:
# 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
Somewhere in the view (I prefer helper methods for such kind of stuff) or in a model you can get, for ex., array of colors Settings.colors.split(/\s/)
. It's very flexible. And you don't need to invent a bike.
Upvotes: 16
Reputation: 174
Try to keep all constant at one place, In my application I have created constants folder inside initializers as follows:
and I usually keep all constant in these files.
In your case you can create file under constants folder as colors_constant.rb
colors_constant.rb
Don't forgot to restart server
Upvotes: 10
Reputation: 6105
According your condition, you can also define some environmental variables, and fetch it via ENV['some-var']
in ruby code, this solution may not fit for you, but I hope it may help others.
Example: you can create different files .development_env
, .production_env
, .test_env
and load it according your application environments, check this gen dotenv-rails which automate this for your.
Upvotes: 0
Reputation: 2032
The global variable should be declare in config/initializers
directory
COLOURS = %w(white blue black red green)
Upvotes: 1
Reputation: 65457
Some options:
Using a constant:
class Card
COLOURS = ['white', 'blue', 'black', 'red', 'green', 'yellow'].freeze
end
Lazy loaded using class instance variable:
class Card
def self.colours
@colours ||= ['white', 'blue', 'black', 'red', 'green', 'yellow'].freeze
end
end
If it is a truly global constant (avoid global constants of this nature, though), you could also consider putting
a top-level constant in config/initializers/my_constants.rb
for example.
Upvotes: 71
Reputation: 59449
A common place to put application-wide global constants is inside config/application
.
module MyApp
FOO ||= ENV.fetch('FOO', nil)
BAR ||= %w(one two three)
class Application < Rails::Application
config.foo_bar = :baz
end
end
Upvotes: 3
Reputation: 5437
Another option, if you want to define your constants in one place:
module DSL
module Constants
MY_CONSTANT = 1
end
end
But still make them globally visible without having to access them in fully qualified way:
DSL::Constants::MY_CONSTANT # => 1
MY_CONSTANT # => NameError: uninitialized constant MY_CONSTANT
Object.instance_eval { include DSL::Constants }
MY_CONSTANT # => 1
Upvotes: 5
Reputation: 403
I typically have a 'lookup' model/table in my rails program and use it for the constants. It is very useful if the constants are going to be different for different environments. In addition, if you have a plan to extend them, say you want to add 'yellow' on a later date, you could simply add a new row to the lookup table and be done with it.
If you give the admin permissions to modify this table, they will not come to you for maintenance. :) DRY.
Here is how my migration code looks like:
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
I use seeds.rb to pre-populate it.
Lookup.find_or_create_by_group_key_and_lookup_key_and_lookup_value!(group_key: 'development_COLORS', lookup_key: 'color1', lookup_value: 'red');
Upvotes: 1
Reputation: 4144
Use a class method:
def self.colours
['white', 'red', 'black']
end
Then Model.colours
will return that array. Alternatively, create an initializer and wrap the constants in a module to avoid namespace conflicts.
Upvotes: 6