Pivotal Labs

Main menu

Skip to primary content
Skip to secondary content
  • About
  • Case Studies
  • Team
    • Executives
    • Locations
      • San Francisco (HQ)
      • Boston
      • Boulder
      • Denver
      • London
      • Los Angeles
      • New York
  • Community
    • Blogs
    • Tech Talks
    • Events
  • Careers
    • Lifestyle
    • Principles & Practices
    • Benefits
    • FAQ
    • Apply
  • Contact
    • Press Room
    • Press Releases
    • In The News
    • Press Kit
  • All
  • Labs
  • Standup
  • Tracker

Redefining Constants

Pivotal Labs
Saturday, April 14, 2007

We all like a good oxymoron, like redefining constants. There are times where we need to redefine a constant to test an edge case in the application code. Before I go into this example, please note that redefining constants is generally not a good way to have maintainable software. If you find yourself needing to redefine a constant, it may be an indication that refactoring is needed.

Given that, lets get into an example where you may need to redefine a constant. Lets say an app has does file uploads to Amazon’s S3 service. A common practice to upload to a real S3 account made for the production, development, or demo environment.

When in the test environment, a fake S3 service would be used instead. The fake service is useful to keep your tests fast and running predictably.

To get a different File Upload service object in each of your environments, one can have the S3 configuration in the environment files:

test.rb

STORAGE_SERVICE = FakeStorageService.new

development.rb

STORAGE_SERVICE = S3StorageService.new("development_service", "access_key", "secret_access_key")

production.rb

STORAGE_SERVICE = S3StorageService.new("production_service", "access_key", "secret_access_key")

The File Upload service objects can be set to constants in the environment file. This works great when testing the logic of the objects that use the File Upload service. However it is a good idea to run an integration test that does a real upload.

Since the tests are running in the test environment, a fake File Upload service is being used. Well now we want to use a real service that points to a test S3 account. An easy trick is to redefine the constant to the S3 service in setup and then redefine the constant back to the fake service on teardown.

There are a few ways of doing this…

Just Reset the Constant

context "A real S3 call" do
  setup do
    STORAGE_SERVICE = S3StorageService.new("test_service", "access_key", "secret_access_key")
  end
  teardown do
    STORAGE_SERVICE = FakeStorageService.new
  end
end

This is the simplest approach, but it produces an error:

warning: already initialized constant STORAGE_SERVICE

###Use silence_warnings

context "A real S3 call" do
  setup do
    silence_warnings do
      STORAGE_SERVICE = S3StorageService.new("test_service", "access_key", "secret_access_key")
    end
  end
  teardown do
    silence_warnings do
      STORAGE_SERVICE = FakeStorageService.new
    end
  end
end

This solution removes the warning, but now a certain section of your code will not have warning at all. Also, one could argue that you lose semantic meaning. It also feels like a hack.

Redefine the Constant

class Module
  def redefine_const(name, value)
    __send__(:remove_const, name) if const_defined?(name)
    const_set(name, value)
  end
end

context "A real S3 call" do
  setup do
    Object.redefine_const(
      :STORAGE_SERVICE,
      S3StorageService.new("test_service", "access_key", "secret_access_key")
    )
  end
  teardown do
    Object.redefine_const(
      :STORAGE_SERVICE,
      STORAGE_SERVICE = FakeStorageService.new
    )
  end
end

Calling redefining the constant does not generate a warning. Also it does provide semantic value because you are actively declaring that you are redefining the constant. If there are other warnings, you will also see them.

Its all Dirty

Redefining constants is a non-standard tatic, especially for those new to Ruby. Since this is unconventional and is often contrary to assumptions, it may lead to unpredictable behavior.

Maybe the storage service can be an attribute that can be changed for individual tests.

  • 0 Shares
  • Share on Facebook
  • Share on Twitter

6 Comments

  1. Chad Woolley says:

    I think this is dirty, and unnecessarily unnecessary. It should be an attribute, not a constant. In an application designed with a Dependency Injection architecture (such as Spring in Java or Needle in Ruby), you can have a global configuration object. This configuration object is easily available to any and all objects in your application, and easily modifiable in tests without jumping through any hoops like redefining constants.

    Unfortunately, Rails doesn’t make it easy to use a dependency injection approach – or at least this isn’t one of the “opinions” that it strongly advocates.

    By the way, this isn’t just speculation, it works great in practice. I’m using it on my open source project (not a Rails app). Whenever I need to simulate a certain configuration for a spec/test context, I create the registry and retrieve the global options object, then set my desired overrides. Then, these options are automatically used everywhere, because every other object is using the same singleton options instance. It’s a singleton, but without the pain – the Registry manages it for you. This is just like the default behavior for all objects created by the Spring framework (to be singletons).

    I’m sure there’s a clean, OOish way to avoid using constants i Rails environment files for global configuration, I’ve just never gone against the grain and tried it yet :/

    Read more about Dependency Injection here:

    • http://martinfowler.com/bliki/InversionOfControl.html
    • http://martinfowler.com/articles/injection.html
    • http://www.theserverside.com/tt/articles/article.tss?l=IOCBeginners
    • (shameless plug) http://www.ibm.com/developerworks/edu/j-dw-java-springswing-i.html

    December 12, 2007 at 11:50 pm

  2. Chad Woolley says:

    Here’s a couple of more recent, Ruby-specific resources on Dependency Injection:

    • http://onestepback.org/index.cgi/Tech/Ruby/DependencyInjectionInRuby.rdoc
    • http://onestepback.org/articles/depinj/

    December 12, 2007 at 11:50 pm

  3. Brian Takita says:

    A configuration object makes more sense, especially the example this article used.

    Sometimes, this isn’t an available option though.
    If you are able to refactor to a configuration object, do it.

    December 12, 2007 at 11:50 pm

  4. Chad Woolley says:

    I wrote a post on an alternative approach that avoids the need to redefine constants:

    http://www.pivotalblabs.com/articles/2007/04/16/avoiding-constants-in-rails

    Can you think of any situations when this approach would not be an available option?

    December 12, 2007 at 11:50 pm

  5. Brian Takita says:

    Having to live with constants rears itself mainly in code you can’t control.

    For example, there is RAILSENV and RAILSROOT and third party code that uses these constants directly. While it is an option to refactor the third-party code, there is overhead in maintaining a branch. The best option in this case would be to submit a patch that creates an accessor for railsenv and railsroot to the project. It can be initialized or lazily defaulted to their respective constants.

    There are a number of techniques to simulate different values for RAILS_ENV.

    • Redefine the constant
    • Set a RAILS_ENV constant in the class you are trying to test
    • RAILS_ENV.replace

    None of these techniques are nearly as appealing as setting a hash or writer method though.

    I can’t think of any other good reasons…

    December 12, 2007 at 11:50 pm

  6. p says:

    Thanks for the helpful post and discussion!

    Here is an example where this technique of redefining a constant is useful, where a configuration object approach can’t help:

    Change the ActionController::Routing::Segment’s RESERVED_PCHAR, SAFE_PCHAR and UNSAFE_PCHAR constant definitions: Either because one is using an earlier RoR where this patch is unavailable: https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/2574-routing-double-escape-url-segments or modifying them to include/exclude a few other characters for the particular app need.

    Cheers,
    Philip

    September 24, 2009 at 9:04 am

Add New Comment Cancel reply

Your email address will not be published.

Pivotal Labs

Pivotal Labs

Recent Posts

  • Does the set of all sets contain itself?
  • Standup 3/8/2012
  • Standup 3/7/2012
Subscribe to Pivotal's Feed

Author Topics

riddles (1)
agile (167)
capistrano (2)
rails (26)
movember (1)
git (10)
railsdoc (1)
object-design (1)
bdd (3)
cucumber (3)
linkedin (1)
oauth (1)
ruby (17)
tdd (2)
lvh.me (1)
rails 3.1.1 (1)
selenium (6)
homebrew (1)
mysql (5)
rvm (1)
sproutcore (1)
paperclip (2)
pry (1)
amazon (1)
heroku (1)
rails3 (2)
jasmine (3)
design (3)
process (12)
productivity (8)
learning (1)
olin (1)
migrations (2)
mongodb (2)
devise (2)
javascript (13)
rubymine (4)
ipad (1)
whurl (1)
head.js (1)
pairing (2)
tools (4)
pair programming (1)
rspec (10)
rspec2 (1)
ruby19 (1)
incubation (3)
startup (5)
api (1)
presenter (1)
vanna (1)
pivotal tracker (5)
capybara (1)
fakeweb (1)
webmock (1)
intern (1)
ruby on rails (25)
meetup (1)
textmate (1)
testing (20)
solr (4)
nyc-standup (11)
community (1)
opensource (3)
activerecord (4)
chrome (1)
mp4 (1)
activeresource (1)
flash (3)
neo4j (1)
nginx (1)
rsoc (1)
meta programming (1)
agile standup (7)
government (3)
webos (4)
xss (1)
jquery (1)
bundler (2)
ci (3)
gems (5)
postgresql (1)
geminstaller (1)
gemcutter (1)
cloud (2)
rack (2)
refraction (1)
gem (5)
refactoring (1)
validations (1)
webrat (1)
engine-yard (1)
firefox (2)
jsunit (1)
mongrel (2)
thin (1)
unicorn (1)
facebook (1)
rubygems (5)
jruby (1)
actioncontroller (1)
rails 2.3 (1)
palmpre (1)
autotest (1)
mac (2)
hosting (1)
goruco (11)
database (3)
railsconf (11)
gogaruco (4)
deployment (4)
github (1)
ie (1)
ajax (1)
intellij (1)
json (1)
asset packaging (1)
polonium (1)
character encoding (1)
utf-8 (1)
test (3)
civics (1)
hpricot (1)
rake (3)
sms (1)
unicode (1)
iphone (1)
java (1)
safari (1)
memory leaks (1)
rr (3)
editor (1)
css (1)
nyc (3)
performance (5)
fun (5)
enterprise rails (1)
health (1)
new and cool (1)
general (2)
treetop (1)
errors (1)
stack (1)
trace (1)
cache (1)
cookies (1)
freesoftware (1)
conferences (1)
development (1)
driven (1)
proxy (1)
caching (1)
peertopatent (1)
languages (1)
rest (2)
rubyforge (1)
sake (1)
file (1)
upload (1)
constants (1)
osx (1)
terminal (1)
pairprogramming (2)
  • About
  • Case Studies
  • Team
  • Community
  • Careers
  • Contact
  • Labs
  • Events

Contact Us

contact@pivotallabs.com
+1 415-77-PIVOT
TwitterLinkedInFacebook

Pivotal Tracker

Tracker is the award-winning agile project management tool that enables real-time collaboration around a shared, prioritized backlog.
Visit pivotaltracker.com >