5

I have been wrestling with this for the last day and it's driving me NUTS!

As an learning exercise I decided that I would package up some of my code into a Rails Gem. This code has a controller action, a route, a model and a helper so I decided that the most suitable method of creating a Gem would be to create it as a Rails Engine.

Everything seems to be working well, except for one thing. When I try to reference the Model from within a Controller or Views (of an application that uses the engine) e.g.:

@su = Shortener::ShortenedUrl.generate("http://stackoverflow.com")

I get the following error:

uninitialized constant Shortener::ShortenerHelper::ShortenedUrl

It's strange because the error doesn't happen when I execute the code from the projects console. I think that this is caused by the fact I have put all of the code into the "Shortener" namespace/module. I did this so it would help avoid conflicts when used within other applications.

The code file hierachy looks like this:

An image of the project directory/file listing

And here is the class/module declaration code (with the guts removed) of the important files in question

app/controllers/shortener/shortened_urls_controller

module Shortener
  class ShortenedUrlsController < ::ApplicationController

    # find the real link for the shortened link key and redirect
    def translate
      # convert the link...
    end
  end
end

app/models/shortener/shortened_urls

module Shortener
  class ShortenedUrl < ActiveRecord::Base

    # a number of validations, methods etc  

  end 
end

app/helpers/shortener/shortener_helper

module Shortener::ShortenerHelper

  # generate a url from either a url string, or a shortened url object
  def shortened_url(url_object, user=nil)

    # some code to do generate a shortened url

  end

end

lib/shortener/engine.rb

require "rails/engine"
require "shortener"

module Shortener

  class ShortenerEngine < Rails::Engine

  end

end

lib/shortener.rb

require "active_support/dependencies"

module Shortener

  # Our host application root path
  # We set this when the engine is initialized
  mattr_accessor :app_root

  # Yield self on setup for nice config blocks
  def self.setup
    yield self
  end

end

# Require our engine
require "shortener/engine"

shortener.gemspec

require File.expand_path("../lib/shortener/version", __FILE__)

# Provide a simple gemspec so you can easily use your enginex
# project in your rails apps through git.
Gem::Specification.new do |s|
  s.name                      = "shortener"
  s.summary                   = "Shortener makes it easy to create shortened URLs for your rails application."
  s.description               = "Shortener makes it easy to create shortened URLs for your rails application."
  s.files                     = `git ls-files`.split("\n")
  s.version                   = Shortener::VERSION
  s.platform                  = Gem::Platform::RUBY
  s.authors                   = [ "James P. McGrath" ]
  s.email                     = [ "gems@jamespmcgrath.com" ]
  s.homepage                  = "http://jamespmcgrath.com/projects/shortener"
  s.rubyforge_project         = "shortener"
  s.required_rubygems_version = "> 1.3.6"
  s.add_dependency "activesupport" , ">= 3.0.7"
  s.add_dependency "rails"         , ">= 3.0.7"
  s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact
  s.require_path = 'lib'
end

I have published the entire code of the engine on GitHub:

https://github.com/jpmcgrath/shortener

NOTE: this engine has a generator to generate the required migration file. Type:

rails g shortener

I have also created a rails 3.1 app that exhibits the problem (look at line 18 of the projects controller):

https://github.com/jpmcgrath/linky

Any ideas? I have scoured the web, but have not been able to find any really definitive guide to making Engine Gems. Any helpers would be much appreciated.

Thanks!

James P McGrath
  • 1,807
  • 20
  • 34
  • There should be some migrations here, I can't test your engine without them. – Benoit Garret Sep 01 '11 at 11:07
  • I've been testing your code from github, it seems to work fine (I only added a migration). What version of rails did you use for your rails app? I used the final 3.1, maybe that's the difference. – Benoit Garret Sep 01 '11 at 11:20
  • Thanks Benoit. The github code has a generator that creates a migration. Just type "rails g shortener". I'll amend my question to include this. – James P McGrath Sep 01 '11 at 12:17
  • As for the rails version, I thought that might be the problem, but I have "gem 'rails', '3.1.0' in my gem file. I will create another github project that I will push my test app to. – James P McGrath Sep 01 '11 at 13:05

1 Answers1

3

In your engine helper (app/helpers/shortener/shortener_helper.rb), replace both occurrences of ShortenedUrl with Shortener::ShortenedUrl.

I found this error weird at the beginning, because ruby is supposed to look for constants in the enclosing module. But helpers are included in another class, which could mean that the constant name resolution environment isn't the same as the one you see in the file.

If you want to know more about namespaced engines and their behaviour, you can look at this excellent answer.

Community
  • 1
  • 1
Benoit Garret
  • 13,552
  • 4
  • 56
  • 64
  • Benoit, that did the trick, you are a legend and have made my day! It's very weird as the code from the helper was working fine when called from the application. I guess things were getting confused somewhere along the way. I should go bone up on ruby name resolution :-) – James P McGrath Sep 01 '11 at 13:40
  • If you find out what exactly happens here, it would make an awesome addition to the answer ;-) – Benoit Garret Sep 01 '11 at 13:42
  • 1
    Shall do. Hopefully some passing Namespace / Name resolution / Engines guru will happen to pass by and be able to provide enlightenment, not that I am looking for an easy way out or anything :-) – James P McGrath Sep 01 '11 at 13:55