What does Ruby constant mean?
That's right, constants are just like variables in ruby, but you get a warning if you change them.
Also, there's one difference with mere variables: You can access constants even if they are defined inside another class or module, for example given this snippet:
module Constants
PI = 3,1415
other = "variable"
end
You can reach PI
doing Constants::PI
while Constants::other
will not work.
Scope of Constants in Ruby Modules
The USER_KEY
you declared (even conditionally) in Auth
is globally known as Auth::USER_KEY
. It doesn't get "mixed in" to including modules, though including modules can reference the key in a non-fully-qualified fashion.
If you want each including module (e.g. ApplicationController
) to be able to define its own USER_KEY
, try this:
module Auth
DEFAULT_USER_KEY = 'user'
def self.included(base)
unless base.const_defined?(:USER_KEY)
base.const_set :USER_KEY, Auth::DEFAULT_USER_KEY
end
end
def authorize
user_id = session[self.class.const_get(:USER_KEY)]
end
end
class ApplicationController < ActionController::Base
USER_KEY = 'my_user'
include Auth
end
If you're going to go to all this trouble, though, you might as well just make it a class method:
module Auth
DEFAULT_USER_KEY = 'user'
def self.included(base)
base.extend Auth::ClassMethods
base.send :include, Auth::InstanceMethods
end
module ClassMethods
def user_key
Auth::DEFAULT_USER_KEY
end
end
module InstanceMethods
def authorize
user_id = session[self.class.user_key]
end
end
end
class ApplicationController < ActionController::Base
def self.user_key
'my_user'
end
end
or a class-level accessor:
module Auth
DEFAULT_USER_KEY = 'user'
def self.included(base)
base.send :attr_accessor :user_key unless base.respond_to?(:user_key=)
base.user_key ||= Auth::DEFAULT_USER_KEY
end
def authorize
user_id = session[self.class.user_key]
end
end
class ApplicationController < ActionController::Base
include Auth
self.user_key = 'my_user'
end
Ruby CONSTANTS seem to be INVISIBLY ALTERABLE?
TL;DR
Short of monkey-patching Kernel#warn (see https://stackoverflow.com/a/662436/1301972) to raise an exception, you won't be able to prevent reassignment to the constant itself. This is generally not a pragmatic concern in idiomatic Ruby code where one expects to be able to do things like reopen classes, even though class names are also constants.
A Ruby constant isn't actually immutable, and you can't freeze a variable. However, you can get an exception to be raised when something attempts to modify the contents of a frozen object referenced by the constant.
Freezing Objects Deeply with Plain Ruby
Freezing an Array is easy:
CONSTANT_ONE = %w[one two three].freeze
but the strings stored in this Array are really references to String objects. So, while you can't modify this Array, you can still (for example) modify the String object referenced by index 0. To solve this problem, you need to freeze not just the Array, but the objects it holds, too. For example:
CONSTANT = %w[one two three].map(&:freeze).freeze
CONSTANT[2] << 'four'
# RuntimeError: can't modify frozen String
CONSTANT << 'five'
# RuntimeError: can't modify frozen Array
Freezing Objects Recursively with a Gem
Since freezing recursive references can be a bit unwieldy, it's good to know there's a gem for that. You can use ice_nine to deep-freeze most objects:
require 'ice_nine'
require 'ice_nine/core_ext/object'
OTHER_CONST = %w[a b c]
OTHER_CONST.deep_freeze
OTHER_CONST << 'd'
# RuntimeError: can't modify frozen Array
OTHER_CONST[2] = 'z'
# RuntimeError: can't modify frozen Array
A Better Way to Use Ruby Constants
Another option to consider is calling Object#dup when assigning the value of a constant to another variable, such as instance variables in your class initializers, in order to ensure you don't mutate your constant's references by accident. For example:
class Foo
CONSTANT = 'foo'
attr_accessor :variable
def initialize
@variable = CONSTANT.dup
end
end
foo = Foo.new
foo.variable << 'bar'
#=> "foobar"
Foo::CONSTANT
#=> "foo"
Ruby - What is difference between defining constant using 'const_set' and simple capitalize constant name in the class?
class A
RANDOM_CONSTANT = 1
end
is simpler to write and read. This should be the preferred way to set a constant.
constant_name = 'RANDOM_CONSTANT'
A.const_set(constant_name, 1)
will work when the constant name is dynamically generated, and thus more flexible. You'd typically use it only when you want to do some metaprogramming magic.
Other than that, they are equivalent.
When to use constants instead of instance variables in Ruby?
There's a few things to consider here:
- Will the value change within the life-cycle of an object?
- Will you need to override the value in sub-classes?
- Do you need to configure the value at run-time?
The best kind of constants are those that don't really change short of updating the software:
class ExampleClass
STATES = %i[
off
on
broken
].freeze
end
Generally you use these constants internally in the class and avoid sharing them. When you share them you're limited in how they're used. For example, if another class referenced ExampleClass::STATES
then you can't change that structure without changing other code.
You can make this more abstract by providing an interface:
class ExampleClass
def self.states
STATES
end
end
If you change the structure of that constant in the future you can always preserve the old behaviour:
class ExampleClass
STATES = {
on: 'On',
off: 'Off',
broken: 'Broken'
}.freeze
def self.states
STATES.keys
end
end
When you're talking about instance variables you mean things you can configure:
class ConfigurableClass
INITIAL_STATE_DEFAULT = :off
def self.initial_state
@initial_state || INITIAL_STATE_DEFAULT
end
def self.initial_state=(value)
@initial_state = value ? value.to_sym
end
end
Constants are great in that they're defined once and used for the duration of the process, so technically they're faster. Instance variables are still pretty quick, and are often a necessity as illustrated above.
How to write constant inside module?
I'm not sure I understand - did you mean this:
module Test1
module Test2
CONSTANT = 5
def self.included(base)
# Where can I declare constant ? How ?
base.extend ClassMethods
base.class_eval do
# named scopes
end
end
module ClassMethods
end
end
end
class A
include Test1::Test2
end
puts A::CONSTANT # => 5
What does it mean when a variable or method or constants has preceded with an underscore in Ruby?
There is no difference in bahavior. It is just convertion.
But let's have a closer look:
_('A string')
is actually a method not a variable. The underscore method is defined by Gettext and translates a string.
@_foo
is offen used to show other developers that there is something special about the variable, and therefore it should not be used. I saw that pattern a lot for variables that are used to cache values, like:
def expensive_operation
@_expensive_operation ||= begin
# long running code...
end
end
And the underscore is sometimes used to indicate that a variable is not used in a block. Like this:
a_hash.each do |_, value|
# do something with the value, not with the key
end
ruby constant scope inside a class self block
You must reference the correct class:
Thing.singleton_class::NUM #=> 3
There is no inconsistency between the way constants and methods in the singleton class are referenced:
Thing.methods.include?(:speak) #=> true
Thing.singleton_class.methods.include?(:speak) #=> false
Thing.singleton_class.instance_methods.include?(:speak) #=> true
Ruby constant within a class method
Not exactly what you wanted, but you simply haven't defined CONST inside class A but in its metaclass, which I have therefore saved a reference to...
class A
class << self
::AA = self
CONST = 1
end
end
puts AA::CONST
Related Topics
Rails 5 Db Migration: How to Fix Activerecord::Concurrentmigrationerror
Problem Using Openstruct with Erb
Testing Routes with Subdomain Constraints Using Rspec
Select Checkbox Pass Array in Ruby on Rails
Obtaining a Facebook Auth Token for a Command-Line (Desktop) Application
How to Set a Hook to Run Code at the End of a Ruby Class Definition
Adding a Background Image in Ruby on Rails 2 in CSS
Ruby on Rails - Doesn't Create Script/Server
How to Serialize an Object Using Tcpserver Inside
-': Nil Can't Be Coerced into Fixnum (Typeerror)
Erroneous "Insecure World Writable Dir Foo in Path" When Running Ruby Script
Your Ruby Version Is 2.1.0, But Your Gemfile Specified 2.0.0
"Encoding::Invalidbytesequenceerror" Error Occurs in Rails 3.1.0
Writing an Activerecord Adapter