tl;dr
I'm trying to break out a part of a larger application as a mountable engine. The engine will exist in different flavors, each contained in their own gem. I can't get the gem name be different from the engine constant name.
Details
The extracted part contains logic for registration, authentication and session handling. The application is used by clients in different parts of the world with different requirements and regulations regarding the end customers using the product. This prompted us to create separate modules for these needs for each regulatory region. They currently live in the lib directory and the different implementations are loaded depending on config.
The goal with the engines is that you mount the appropriate engine and the routes file routes all the concerned calls to the engine.
Since we have several such registration modules, and more to come, we need to maintain several gems for the variants. I'm trying to make it so that the gems have different names (auth_A, auth_B, etc) but the contained engine has the same contant name, Auth::Engine.
That way we can include the correct gem in the Gemfile and the rest will just work since the endpoint it should rout to is always the same regardless of what version is running.
Problem
The problem I run in to is that I can't seam to get the gem to have one name and the engine constant another...
If I generate a new engine names auth it works fine to mount it in the main app. If I then change the gem name and containing folder to auth_a and update the host apps Gemfile it stops working, I can bundle fine but when I try to start the host app it fails when it ties to mount the engine, complaining that Auth::Engine is an undefined constant.
My, slightly redacted, gemspec looks like this:
$:.push File.expand_path("../lib", __FILE__)
# Maintain your gem's version:
require "auth/version"
# Describe your gem and declare its dependencies:
Gem::Specification.new do |s|
s.name = "auth_a"
s.version = Auth::VERSION
s.authors = ["Jonas Schubert Erlandsson"]
s.email = ["jonas.schubert.erlandsson#xxxxxx.com"]
s.homepage = "http://some.page.on/the/internet.html"
s.summary = "Authentication module for A"
s.description = "This engine, when mounted in Host app, handles registration, account update, login and logout of users."
s.files = Dir["{app,config,db,lib}/**/*"] + ["MIT-LICENSE", "Rakefile", "README.rdoc"]
s.test_files = Dir["test/**/*"]
s.add_dependency "rails", "~> 3.2.13"
end
The only thing I have changed from the generated scaffold is s.name = "auth_a". The relevant line from the host apps Gemfile: gem 'auth_a', path: "../auth_a"...
I have looked through the entire source tree trying to find where it infers a name from the gem name but I can't see it. I don't know what I'm missing and the gem spec docs weren't much help for this either ... I didn't think that the gem name was bound to the constant names of the gem, but maybe I'm wrong? Can you override it? Or am I missing something else?
The answer to this was to simply add this to the line in the host apps Gemfile: gem 'auth_a', path: "../auth_a", require: 'auth'.
So it looks like it defaults to requiring, that is auto loading, a constant based on the gem name, but the require call tells it what to require instead.
Hope it helps someone else as well :)
Related
I have a GoogleTranslate service that translates text in my app. The feature works on localhost but in production it raises
uninitialized constant Google::Cloud in my app/helpers/google_translations_helper.rb:4:in `initialize'...
Here is the Gemfile related part :
# use of google API
gem 'google-api-client', '~> 0.11', :require =>
'google/apis/analyticsreporting_v4'
gem 'omniauth-google-oauth2'
gem "google-cloud-translate"
And here is the helper/service initializer:
module GoogleTranslationsHelper
class GoogleTranslate
def initialize
#translation_service = Google::Cloud::Translate.new
end
def translate(text)
#translation_service.translate text.to_s, from: "fr", to: "en"
end
end
end
I wonder if I'm not missing something about gem version or something like that..
Does someone had the problem already?
I assume you are running Rails. Make sure you include:
require "google/cloud/translate"
somewhere reasonable, either at the top of the file that creates the client object (app/helpers/google_translations_helper.rb in your case), or in a global initialization file such as config/application.rb. (The google-cloud-translate library, like most libraries, needs you to require it before you can use it. See the snippets in the documentation for examples.)
It's not completely clear to me why this is working differently between your development and production environments, but there are usually a lot of differences in the initialization procedure between the two environments so it's not too surprising. Just make sure you're in the habit of requiring any library before using it.
As a side note, I would also recommend updating your Gemfile to call for more recent versions of the Google client libraries. Or at least make sure you've done a recent bundle update. As of this writing, google-api-client 0.11 is more than 2 years old; the newest is 0.30.8. And google-cloud-translate is at 1.3.0. It is always possible there are issues if you're on old versions.
I have a higher level app, that includes two engines:
Core (engine)
API (engine)
The API engine depends on the models present in the Core engine. I was wondering how to reference these.
What I have tried
For example, if the Core engine has the model "User", how do I reference it via the API engine?
module Api
describe User do
routes { Api::Engine.routes }
before :each do
::Core::User.create!
end
....
With this code, I received:
Failure/Error: ::Core::User.create!
NameError:
uninitialized constant Core
So I thought that I had to include the core engine in the api.gemspec file.
s.add_dependency "core", path: "core/"
however, looks like bundler did not like that.
There was a Gem::Requirement::BadRequirementError while loading
api.gemspec:
Illformed requirement [{:path=>"core/"}] from myAppPath/api/api.gemspec:21:in
I also tried
s.add_dependency "core", path: "../core/"
but that gave me a similar error.
Any idea what should be done to reference the Core models from the API engine?
Thanks!
Update
I attempted to add the Core engine into the Api engine via the Api's Gemfile. Unfortunately, I get an error. I'm starting to feel that engine's should not reference other engines. Would that be true?
/home/.rvm/gems/ruby-2.0.0-p247/gems/railties-
4.0.0/lib/rails/application/routes_reloader.rb:10:in `rescue in execute_if_updated':
Rails::Application::RoutesReloader#execute_if_updated delegated to
updater.execute_if_updated, but updater is nil: #
<Rails::Application::RoutesReloader:0x007fb53b67bce8 #paths=
["/home/myApp/api/spec/dummy/config/routes.rb", "/home/myApp/core/config/routes.rb",
"/home/myApp/api/config/routes.rb"], #route_sets=[#
<ActionDispatch::Routing::RouteSet:0x007fb53b3b8420>, #
<ActionDispatch::Routing::RouteSet:0x007fb53b6008b8>, #
<ActionDispatch::Routing::RouteSet:0x007fb53b6b9b60>]> (RuntimeError)
UPDATE on findings
In addition to the answer below, I would like to add that .gemspec files DO NOT have any information on where the gem will be stored, i.e. it will not point to a git repository, or a file path, etc. This is explained in this article:
http://yehudakatz.com/2010/12/16/clarifying-the-roles-of-the-gemspec-and-gemfile/
There are 2 things to understand here:
If your Engines are supposed to be public (like Devise engine for example), then you'll want to reference your Core dependency in the gemspec. You'll not be able to reference it using path though. But for public engines, this is not a matter since public engines will be on Rubygems at some point in time.
If your Engines are private and used only to cleanup your code base, then adding gem 'core', path: '../core' to your Engine's Gemfile is fine.
The thing to remember here:
gemspec is for dependencies declaration (the gems your engine needs in order to work)
Gemfile is for local or test dependencies declaration
I have the simple following code, which is working in a ruby (not rails) app:
require 'gmail'
Gmail.new('my_account', 'my_password') do |gmail|
end
I am able to get a connection to the Gmail account and do some stuff in there.
However, I want to use this Gem in a Rails app, and therefore I have tried adding the following into the Gemfile:
gem "ruby-gmail", "0.2.1"
gem "mime", "0.1"
However, when I try to use this in a rake task, like this:
task :scrap_receipts_gmail => :environment do
Gmail.new('my_account', 'my_password') do |gmail|
puts gmail.inspect
end
end
I get the following error:
uninitialized constant Object::Gmail
This is solved if I add require 'gmail'. My question is:
Why would I have to require gmail, if I have already specified that in the Gemfile?
The module/class namespace has to match the directory structure. For example, in lib/foo/bar.rb, if and only if the namespace is Foo::Bar can it be auto loaded by Rails, otherwise you have to require it explicitly.
In this case, Gmail is defined as a class, which doesn't match the directory structure. If Gmail was defined as a module (namespace ::Gmail matchs directory structure), then you'll never need to explicitly require "gmail".
As part of the RSpec, i need to reference a file contained in a gem I am depending on (call it gem foo). I control gem foo, so I can make changes to it as needed.
Gem 'foo' contains a file that I need to reference with in the a rspec spec. Is there a reasonably stable RubyGem or Bundler API to figure out 'foo' base directory?
Assuming 'foo' is already required in my Gemfile:
in Gemfile:
gem 'foo'
I want to do something like this (in something_spec.rb):
filename = File.expand_path('examples/xml/result.xml', Gem.gem_base_path('foo'))
What is gem_base_path API call?
I would recommend creating a function in your gem to do this:
module Foo
class Configuration
def self.result_xml_path
File.realpath("../examples/xml/result.xml")
end
end
end
You can then do the following in your spec:
filename = Foo::Configuration.result_xml_path
This is much safer since you are getting all the information from the gem. It also looks cleaner.
This may do what you need without ant need to touch the 'foo' gem:
matches = Gem::Specification.find_all_by_name 'foo'
spec = matches.first
filename = File.expand_path('examples/xml/result.xml', spec.full_gem_path)
I have used this code to make something similar to what you need (namely loading in my specs some factories defined in a gem used by my project)
I have a engine style Rails plugin from which I can create a gem using Jeweler. But when I require it in my Rails environment (or erb) the models within the plugin are not loaded. I have followed a number of tutorials and read just about everything on the subject.
# environment.rb
config.gem 'myengine'
# in irb
require 'myengine'
I have unpacked the gem and verified that all files are present. My init.rb has been moved to a new folder called 'rails' as per. All files in 'lib' are automatically added to the $LOAD_PATH, so require 'myengine' runs lib/myengine.rb. I verified this by putting a puts 'hello' within.
Is it because of the physical presence of plugins in a known place that Rails can add all the models, controller etc. to the relevant load_paths? Do I need to replicate this manually when using a gem?
Would gemspec require_paths be a way of adding additional paths other than lib? I assume however that Rails does not just require every single file, but loads them on demand hence the need for the filename and class name to match?
%w{ models controllers helpers }.each do |dir|
path = File.join(File.dirname(__FILE__), 'app', dir) + '/'
$LOAD_PATH << path
puts 'requiring'
Dir.new(path).entries.each do |file|
if file =~ /\.rb/
puts file
require file
end
end
end
By adding the above to lib/myengine.rb all the models/controllers are required. But like I said in my question this is unlikely to be a good way forward.
Offhand I'd say the part about adding those directories to the search path is right on. What you shouldn't need to do is require each file manually (as you allude to in your last sentence). What Rails does when you reference a non-existent constant is to search for a file with the same name (underscored of course) in the load path.
If for some reason you can not abide by the constraint (think about it long and hard) then you are going to need to dig deeper into Rails and see how the reloading mechanism works so you can tie into it properly in development mode.
The problem was the files (in app) where not being added to the gem because when using Jeweler it only automatically adds files to required_paths which are committed to git.