I'm building a mountable engine that is dependent on another 'core' (unmounted) engine that I have written.
In my container app's Gemfile I add the core engine and the optional engine's git repo's.
In my mountable engine, where should I add its dependencies on the 'core' engine to be used in the dummy app for testing? (rspec)
I tried adding this in the mountable engine's gemspec:
require "my_core"
...
s.add_dependency "my_core", :git => "https//github.com/me/my_core.git"
I am doing this by declaring the dependency in the engine Gemfile
if ENV['LOAD_GEMS_FROM_LOCAL'] == '1'
gem 'my_core', path: File.expand_path("../../my_core", __FILE__)
else
gem 'my_core', git: 'https//github.com/me/my_core.git'
end
The LOAD_GEMS_FROM_LOCAL lets me load the other engine from the filesystem, so I can develop both engines at the same time.
Related
I have a complex Rails app and I want to extract some core functionality into an engine so that I can reuse the models etc in other Rails apps.
I've been following the official documentation for engines (https://guides.rubyonrails.org/engines.html). I'm able to create a new engine inside the app and generate some test models
> rails plugin new testengine --mountable
testengine> rails generate model Test
This is the .gemspec
require_relative "lib/testengine/version"
Gem::Specification.new do |spec|
spec.name = "testengine"
spec.version = Testengine::VERSION
spec.authors = ["Me"]
spec.summary = "testengine"
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
# to allow pushing to a single host or delete this section to allow pushing to any host.
spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
spec.files = Dir["{app,config,db,lib}/**/*", "Rakefile", "README.md"]
spec.add_dependency "rails", "~> 6.1.4"
end
I console into the test dummy rails app in testengine, and I can find my new model at Testengine::Test, no problem. So far so good.
Now I get to section 4.1 Mounting the Engine. I add the engine via the Gemfile file (in fact this is already done for me thanks to the rails generator above).
gem 'testengine', path: 'testengine'
Then I install my gems without problems.
> bundle install
...
Using testengine 0.1.0 from source at `testengine`
...
I console into the main app and I can find Testengine and Testengine::VERSION but not Testengine::Engine or Testengine::Test.
Reading a little further the docs say you need add this line to config/routes.rb
mount Testengine::Engine, at: "/testengine"
I do and now the rails app won't even start
config/routes.rb:3:in `block in <top (required)>': uninitialized constant Testengine::Engine (NameError)
What did I miss?
I will answer my question for the benefit of others who might make the same mistake I made. In my case, gem 'testengine', path: 'testengine' was buried inside a group of gems e.g. in
group :test do
...
end
I guess I was confused how rails loads gems from groups and missed the detail about group inclusion. It seems that while it'll show in the list during bundle install, and autoload some basic items, such as Testengine::VERSION it doesn't autoload everything unless you are running an environment as the same name as the group. In hindsight, this seems a bit obvious. Lesson learned.
I'm creating a Rails web application which uses a shared library of models (as a Rails Engine), stored in a subrepository (Git subtree.) This shared library contains dependencies on other Ruby gems (in my case, HTTParty and Dalli), of which I want to be automatically referenced by the parent project that includes this shared library.
However, my gem's dependencies don't appear to be resolving in the parent project, and when I start my web application, it has missing references to those gem dependencies in the shared library. (i.e. NameError: uninitialized constant ApiClient::HTTParty) If I explicitly add those references to my web app's Gemfile (as in uncomment the Gemfile lines below), everything works fine.
How do I get these dependencies to 'chain', and have the parent project automatically resolve these references?
Here's what my project looks like:
[MyRailsApp]
-- ...
-- [app]
-- [config]
-- [lib]
-- [MyLib]
-- ...
-- [app]
-- [config]
-- [lib]
-- [MyLib]
-- version.rb
-- engine.rb
-- MyLib.gemspec
-- Gemfile
-- Gemfile
MyRailsApp/Gemfile:
source 'https://rubygems.org'
gem 'activesupport', '3.2.13', :require => 'active_support'
gem 'actionpack', '3.2.13', :require => 'action_pack'
gem 'actionmailer', '3.2.13', :require => 'action_mailer'
gem 'railties', '3.2.13', :require => 'rails'
...
# gem 'dalli'
# gem 'httparty'
gem 'MyLib', :path => 'lib/MyLib'
MyLib/MyLib.gemspec:
$:.push File.expand_path("../lib", __FILE__)
# Maintain your gem's version:
require "mylib/version"
# Describe your gem and declare its dependencies:
Gem::Specification.new do |s|
s.name = "MyLib"
s.version = MyLib::VERSION
s.authors = ["David"]
s.email = ["ops#myemail.com"]
s.homepage = "http://www.mysite.com"
s.summary = "Shared Library"
s.description = "Shared Library"
s.files = Dir["{app,config,db,lib}/**/*"] + ["MIT-LICENSE", "Rakefile", "README.rdoc"]
s.add_dependency "rails", "~> 3.2.13"
s.add_dependency "dalli", ">= 2.6.4"
s.add_dependency "httparty", ">= 0.11.0"
end
Figured it out. I had the wrong line of thinking... I thought Gemspecs, used for installing gem dependencies (which was working just fine), would also be used by Rails to determine what dependencies need to be loaded into memory when the application starts. This is not the case, at least, not when using Rails engines.
For the average gem, it appears that a typical Rails web app has a line in the boot.rb file which loads all gems and dependencies in the application Gemfile. However, this autoloading does not appear to extend to Rails engines listed in the Gemfile. In this case, you must load your dependencies into application memory manually, by finding the engine.rb file (in your Rails engine) and adding require 'yourgem' at the beginning of the file. This will load the dependency when the engine loads.
A friend found and linked me this relevant question/answer, if this explanation isn't sufficient:
https://stackoverflow.com/a/5850503
If you want rubygems to understand your dependencies, package each of them with a proper .gemspec file. You don't have to publish your gem, it can be private and referenced via a git:// type URL.
The thing is, generally your .gemspec needs to be at the root level. You can't bury it in your project as Rubygems does not go out of its way to look for these.
In your use case, MyLib should be a separate thing.
I have written some custom generators for my engine to simplify my work.
I want to generate some files direct into my engine.
However the generator is accessible only from the dummy app and not direct from the engine.
my_engine_root:# rails g custom_generator
Could not find generator custom_generator.
engine_dummy_path:# rails g custom_generator
Everything works...
I tried to require the generators path, but nothing worked. What am I doing wrong?
First of all, isolate your custom generator to a separated engine, so you can add this excerpt to "script/rails" or "bin/rails", depending on rails version, in the engine which you want the generators available:
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
then add rails, your custom generator gem and all dependencies to the Gemfile of your engine
gem 'rails', '~> 4.0.2'
gem 'my_custom_generator', :path => "/path/to/my_custom_generator"
I'm not sure if this sort of thing is very common, but I keep finding myself trying to create gems that are just wrappers around a Rails application.
My gem will have a generator to create a config.ru but the Rails application will live inside the gem's lib directory. I need to know how to "embed" a Rails application and configure it so that it can be run inside the gem.
For example:
$ mygem new project
mygem created a directory called "project" with the following files:
project/config.ru
project/widgets/
project/foobars/
My gem will also generate some directories that will need to be added to Rails somehow so that I can access the code in those directories from the Rails app living inside the Gem.
Any help or advice you can give me would be appreciated.
To clarify, I'm not trying to create a Rails engine, or plugin to a Rails application. I'm trying to create a fully-fledged Rails application, but package it as a gem so that a user of my gem can run the gem (the rails app) without needing to know that it's using Rails behind the scenes.
Update: Okay, I've got a little bit working now. I've created the gem and generated the rails project inside the gem's lib directory.
$ bundle gem my_gem && cd my_gem/lib
$ rails new my_gem --skip-bundle
Which leaves me with:
my_gem/
my_gem.gemspec
bin/my_gem
lib/
my_gem.rb
my_gem/
version.rb # generated by bundler
# the rails app:
app/
config/
Gemfile
...etc
Since this gem requires Rails, I started adding the gems defined in the Rails Gemfile as dependencies in the gem's Gemspec, but I'm a little confused as to how to handle the assets group in the Gemfile.
# Rails Gemfile
group :assets do
gem 'sass-rails', '~> 3.2.3'
gem 'coffee-rails', '~> 3.2.1'
gem 'therubyracer', :platforms => :ruby
gem 'uglifier', '>= 1.0.3'
end
# gemspec
Gem::Specification.new do |gem|
gem.name = "my_gem"
# ...
gem.add_dependency 'rails', '3.2.8'
gem.add_dependency 'sqlite3'
gem.add_dependency 'jquery-rails'
# how to add the assets group gems?
end
Try this and see if it helps you make progress.
Gems are just directories of files, and you can put whatever files you want into a gem.
Create:
Create a blank gem full-blown Rails project:
$ bundle gem my_gem
Then a Rails app:
$ rails new my_app --skip-bundle
Copy the Rails files into the gem:
$ cp -R my_app/* my_gem
Bundle everything into your Rails app:
$ cd my_gem
$ bundle install --binstubs --path vendor/bundle
$ cd -
Make the Rakefile have the gem tasks and the Rails setup:
#!/usr/bin/env rake
require "bundler/gem_tasks"
require File.expand_path('../config/application', __FILE__)
MyApp::Application.load_tasks
Verify that it starts:
$ rails server
Load Path:
To control where Rails looks for files, such as "external" configuration files, you can use the file config/application.rb with any directory paths like this:
# Add additional load paths for your own custom dirs
# config.load_paths += %W( #{config.root}/../customdir )
Note the ".." which means go above the Rails directory. This gives you a path relative to the gem.
If you prefer you can specify an absolute path, for example if you know the user will always keep his external files in "~/myfiles/". You can also choose to use ENV vars to send in whatever directory you want.
If you read about load path capabilties, look for lines that are shorthand for adding a directory to the front of the load path because you may want to put your external diretories first:
$:.unshift File.dirname(__FILE__)
Gem Build:
Edit my_gem.gemspec to add your own description, homepage, summary, etc. then build:
$ gem build my_gem.gemspec
Successfully built RubyGem
Name: my_gem
Version: 0.0.1
File: my_gem-0.0.1.gem
Now your Rails app is packaged as a gem.
The config.ru should be a typical Rails one. No special changes AFAIK.
When your user wants to install your app:
$ gem install my_gem
The gem will install in the user's typical gem directory. If you want to adjust this, see this page on rubygems: http://docs.rubygems.org/read/chapter/3
Crate:
You may also want to investigate the Crate project:
Crate: Packaging Standalone Ruby Applications
http://www.slideshare.net/copiousfreetime/crate-packaging-standalone-ruby-applications
Rack:
To use config.ru here is the typical Rails setup:
# Rails.root/config.ru
require "config/environment"
use Rails::Rack::LogTailer
use ActionDispatch::Static
run ActionController::Dispatcher.new
For your project, you want to require some files before Rails. You'll want to learn about the Ruby "require" and how it finds files using LOAD_PATH.
The easy way:
# Rails.root/config.ru
require_relative 'filename'
require "config/environment"
Or to put the user's custom directory up couple directory levels:
require './../../filename' # not the best for security
Or to use an absolute path, read about File.expand_path:
File.expand_path(__FILE__)
Or to use the current directory and put it on the load path:
$LOAD_PATH.unshift(File.dirname(__FILE__))
require 'filename'
Lots of choices for you to consider. Hope this helps!
What about the question, "How am I going to run the Rails application inside the gem?".
A Rails application has controllers and views to run a web server. What you need are actions to create, list, update, and destroy. Exposing these actions without a web server is essentially having such methods in a class. That's a normal standard type of gem in the first place.
So maybe your questions is really, how do I write a gem where I have ActiveRecord, and the other Rails stuff.
First, you need to make your gem dependent on the Rails gems you need. You do this in the gemspec file for your gem.
Then it really is just a matter of your gem code doing a require of the right Rails gems you need.
I'm not sure if this will help, as I read through everything and I couldn't find the motivation behind why you were doing this. One of the reasons I came up with was making something that can be used on a desktop environment. In that case you could try using something like Bowline. If you just want to provide an application that others can download and use and install themselves, then you can probably assume they can follow at least basic developer kind of instructions and you could just provide the whole app on github or as a zip file. See an example of someone else doing something similar over on Fat Free CRM's github page.
Let's say I have created a mountable engine into ~/my_engine folder:
rails plugin new my_engine --mountable
How do I mount this engine into a Rails 3.1 app, that is at the same directory level (e.g. ~/my_app)?
There is a good writeup of the process here:
http://www.builtfromsource.com/2010/12/13/mountable-engines-in-rails-3-1-beta-getting-started/
In short, add this to your main app's Gemfile:
gem 'my_engine', :path => '../my_engine'
And run bundle install/bundle update. Add this to your main app
mount MyEngine::Engine => '/my-engine-url'