Rails 3.1: Engine vs. Mountable App - ruby-on-rails-3

Can someone help me understand the differences between a Rails Engine and a Mountable app? In Rails 3.1, you can create either one with the "rails new plugin ___" command.
rails plugin new forum --full # Engine
rails plugin new forum --mountable # Mountable App
When would you want to use one versus the other? I know you can package an Engine as a gem, for one. Is that not the case for Mountable Apps? What other differences are there?

I have noticed the following:
Full Engine
With a full engine, the parent application inherits the routes from the engine. It is not necessary to specify anything in parent_app/config/routes.rb. Specifying the gem in Gemfile is enough for the parent app to inherit the models, routes etc. The engine routes are specified as:
# my_engine/config/routes.rb
Rails.application.routes.draw do
# whatever
end
No namespacing of models, controllers, etc. These are immediately
accessible to the parent application.
Mountable Engine
The engine's namespace is isolated by default:
# my_engine/lib/my_engine/engine.rb
module MyEngine
class Engine < Rails::Engine
isolate_namespace MyEngine
end
end
With a mountable engine, the routes are namespaced and the parent app can bundle this functionality under a single route:
# my_engine/config/routes.rb
MyEngine::Engine.routes.draw do
#whatever
end
# parent_app/config/routes.rb
ParentApp::Application.routes.draw do
mount MyEngine::Engine => "/engine", :as => "namespaced"
end
Models, controllers, etc are isolated from the parent application - although helpers can be shared easily.
These are the main differences I have spotted. Perhaps there are others? I have asked over here, but have yet to receive a response.
My impression is that since a full engine does not isolate itself from the parent application, it is best used as a standalone application adjacent to the parent app. I believe name clashes could occur.
A mountable engine could be used in situations where you want to avoid name conflicts and bundle the engine under one specific route in the parent application. For example, I am working on building my first engine designed for customer service. The parent application could bundle it's functionality under a single route such as:
mount Cornerstone::Engine => "/cornerstone", :as => "help"
If I'm way off in my assumptions, someone please let me know and I'll fix this response. I have made a small article about the subject here.

Both options will generate an engine. The difference is that --mountable will create the engine in an isolated namespace, whereas --full will create an engine that shares the namespace of the main app.
The differences will be manifested in 3 ways:
1) The engine class file will call isolate_namespace:
lib/my_full_engine/engine.rb:
module MyFullEngine
class Engine < Rails::Engine
end
end
lib/my_mountable_engine/engine.rb:
module MyMountableEngine
class Engine < Rails::Engine
isolate_namespace MyMountableEngine # --mountable option inserted this line
end
end
2) The engine's config/routes.rb file will be namespaced:
Full engine:
Rails.application.routes.draw do
end
Mounted engine:
MyMountableEngine::Engine.routes.draw do
end
3) The file structure for controllers, helpers, views, and assets will be namespaced:
create app/controllers/my_mountable_engine/application_controller.rb
create app/helpers/my_mountable_engine/application_helper.rb
create app/mailers create app/models
create app/views/layouts/my_mountable_engine/application.html.erb
create app/assets/images/my_mountable_engine
create app/assets/stylesheets/my_mountable_engine/application.css
create app/assets/javascripts/my_mountable_engine/application.js
create config/routes.rb create lib/my_mountable_engine.rb
create lib/tasks/my_mountable_engine.rake
create lib/my_mountable_engine/version.rb
create lib/my_mountable_engine/engine.rb
Explanation
The use case for the --full option seems to be very limited. Personally I can't think of any good reason why you'd want to separate your code into an engine without isolating the namespace as well- It would essentially just give you two tightly coupled applications sharing identical file structures and all the conflicts and code leakage that entails.
Every piece of documentation I've seen demonstrates the --mountable option, and indeed the current edge guide strongly encourages you to include isolate namespace- which is the same as saying use --mountable over --full.
Finally there's terminology confusion: Unfortunately rails plugin -h shows the following descriptions:
[--full] # Generate a rails engine with bundled Rails application for testing
[--mountable] # Generate mountable isolated application
This gives the impression that you use --full to create an "engine" and --mountable to create something else called a "mountable application", when in fact they're both engines - one namespaced and one not. That's bound to lead to confusion as users looking to create an engine will likely assume that --full is the more relevant option.
Conclusion
rails plugin new something --full = Engine in your app's namespace. (Why would you?)
rails plugin new something --mountable = Engine with it's own namespace. (Awesome)
References
http://edgeguides.rubyonrails.org/engines.html
http://api.rubyonrails.org/classes/Rails/Engine.html
http://railscasts.com/episodes/277-mountable-engines
https://github.com/rails/rails/pull/6499

i was wondering the same and, hence, ended up here. it seems to me that the earlier answers basically cover the question, but i thought the following might help as well:
# generate plugins (NOTE: using same name each time to minimize differences)
# -----------------------------------------------------------------------------
$ rails plugin new test-plugin -T
$ mv test-plugin{,.01}
$ rails plugin new test-plugin -T --mountable
$ mv test-plugin{,.02}
$ rails plugin new test-plugin -T --full
$ mv test-plugin{,.03}
$ rails plugin new test-plugin -T --full --mountable
$ mv test-plugin{,.04}
# compare "stock" (01) with "mountable" (02)
# -----------------------------------------------------------------------------
$ diff -r test-plugin.01 test-plugin.02
Only in test-plugin.02: app
Only in test-plugin.02: config
Only in test-plugin.02/lib/test-plugin: engine.rb
diff -r test-plugin.01/lib/test-plugin.rb test-plugin.02/lib/test-plugin.rb
0a1,2
> require "test-plugin/engine"
>
Only in test-plugin.02: script
diff -r test-plugin.01/test-plugin.gemspec test-plugin.02/test-plugin.gemspec
18a19
> # s.add_dependency "jquery-rails"
# compare "stock" (01) with "full" (03)
# -----------------------------------------------------------------------------
$ diff -r test-plugin.01 test-plugin.03
Only in test-plugin.03: app
Only in test-plugin.03: config
Only in test-plugin.03/lib/test-plugin: engine.rb
diff -r test-plugin.01/lib/test-plugin.rb test-plugin.03/lib/test-plugin.rb
0a1,2
> require "test-plugin/engine"
>
Only in test-plugin.03: script
diff -r test-plugin.01/test-plugin.gemspec test-plugin.03/test-plugin.gemspec
18a19
> # s.add_dependency "jquery-rails"
# compare "mountable" (02) with "full" (03)
# -----------------------------------------------------------------------------
$ diff -r test-plugin.02 test-plugin.03
Only in test-plugin.03/app/assets/javascripts/test-plugin: .gitkeep
Only in test-plugin.02/app/assets/javascripts/test-plugin: application.js
Only in test-plugin.03/app/assets/stylesheets/test-plugin: .gitkeep
Only in test-plugin.02/app/assets/stylesheets/test-plugin: application.css
Only in test-plugin.03/app/controllers: .gitkeep
Only in test-plugin.02/app/controllers: test-plugin
Only in test-plugin.03/app/helpers: .gitkeep
Only in test-plugin.02/app/helpers: test-plugin
Only in test-plugin.03/app/mailers: .gitkeep
Only in test-plugin.03/app/models: .gitkeep
Only in test-plugin.03/app/views: .gitkeep
Only in test-plugin.02/app/views: layouts
diff -r test-plugin.02/config/routes.rb test-plugin.03/config/routes.rb
1c1
< TestPlugin::Engine.routes.draw do
---
> Rails.application.routes.draw do
diff -r test-plugin.02/lib/test-plugin/engine.rb test-plugin.03/lib/test-plugin/engine.rb
3d2
< isolate_namespace TestPlugin
# compare "mountable" (02) with "full & mountable" (04)
# -----------------------------------------------------------------------------
$ diff -r test-plugin.02 test-plugin.04
<no difference>
# compare "full" (03) with "full & mountable" (04)
# -----------------------------------------------------------------------------
$ diff -r test-plugin.03 test-plugin.04
Only in test-plugin.03/app/assets/javascripts/test-plugin: .gitkeep
Only in test-plugin.04/app/assets/javascripts/test-plugin: application.js
Only in test-plugin.03/app/assets/stylesheets/test-plugin: .gitkeep
Only in test-plugin.04/app/assets/stylesheets/test-plugin: application.css
Only in test-plugin.03/app/controllers: .gitkeep
Only in test-plugin.04/app/controllers: test-plugin
Only in test-plugin.03/app/helpers: .gitkeep
Only in test-plugin.04/app/helpers: test-plugin
Only in test-plugin.03/app/mailers: .gitkeep
Only in test-plugin.03/app/models: .gitkeep
Only in test-plugin.03/app/views: .gitkeep
Only in test-plugin.04/app/views: layouts
diff -r test-plugin.03/config/routes.rb test-plugin.04/config/routes.rb
1c1
< Rails.application.routes.draw do
---
> TestPlugin::Engine.routes.draw do
diff -r test-plugin.03/lib/test-plugin/engine.rb test-plugin.04/lib/test-plugin/engine.rb
2a3
> isolate_namespace TestPlugin
of particular interest (to me) is the fact that there is no difference between
rails plugin new test-plugin -T --mountable
and
rails plugin new test-plugin -T --full --mountable

My understanding of the difference is that engines are like plugins, and add functionality to existing applications. While mountable apps are essentially an application, and can stand alone.
So if you want to be able to run it by itself or within another application you would make a mountable app. If you intend for it to be an addition to existing applications, but not run by itself you would make it an engine.

The difference, I believe, is that a mountable app's are isolated from the host app, so they can't share classes - models, helper etc. This is because a Mountable app is a Rack endpoint (i.e a Rack app in its own right).
Disclaimer: I have, like most, only just started toying with Rails 3.1.

Related

How to get the error log from SASS when using the rails asset pipeline?

I've got 3 Rails 3.2 applications using the gem jquery-ui-themes. jquery-ui-themes uses scss for the image-path helper.
It works great on two of my applications, but the 3rd doesn't seem to compile the scss files in either development or production modes.
IOW, it sends this to the browser
background: #fcfdfd url(image-path("jquery-ui/redmond/ui-bg_inset-hard_100_fcfdfd_1x100.png")) 50% bottom repeat-x;
whereas the two working apps properly send
background: #fcfdfd url("/assets/jquery-ui/redmond/ui-bg_inset-hard_100_fcfdfd_1x100.png") 50% bottom repeat-x;
I've spent many hours trying to make the app that's broken as similar to possible to the two working apps as I can, but it's still failing.
My theory is that SASS is choking on something previous to redmond.css.scss. If so, there should be an error logged somewhere. Where do I find the error output from SASS?
Update:
I introduced a deliberate error into redmond.css.scss and I got a proper error dump. So I know that I'm correctly clearing the cache and actually running sass. Now to figure out why it's ignoring the image-path directives.
If you're looking for the answer to the question in the title, the answer is "the same way you get any other errors: they're in your log". To get a full backtrace, just point your browser at the asset: ie http://localhost:3000/assets/jquery-ui/redmond.css in my case.
Make sure you clear all your caches: rm -rf .sass-cache/ && rm -rf public/assets && rake tmp:cache:clear, as well as using ctrl-shift-r in your browser.
If you're looking for the answer for my real problem (image-path not working), make sure you have the proper Bundler.require line in your application.rb. The old Rails 3.0 doesn't work.
replace:
# If you have a Gemfile, require the gems listed there, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(:default, Rails.env) if defined?(Bundler)
with:
if defined?(Bundler)
# If you precompile assets before deploying to production, use this line
Bundler.require(*Rails.groups(:assets => %w(development test)))
# If you want your assets lazily compiled in production, use this line
# Bundler.require(:default, :assets, Rails.env)
end

How to access Rails 3 engine models in rails console

In the mail app's rails console (irb), how to access engine's models.
update: Say "team" is my main app and "team_page" is the engine. "team_page" is required in main app in the gemfile through 'gem => "team_page", :path => "local/path/to/team_page"'.
when I go to team's rails console, I couldn't access team_page's models.
First you must know the module's name. To help with that, you can run a
bundle show team_page
to find its directory and explore over there (probably under lib/team_page.rb) until
you see the following definition:
module TeamPage
# ...
end
Let's say that the module is called TeamPage. Then just prepend double colon to its name like this:
::TeamPage::SomeModel.some_method

Using Compass on Heroku: /tmp for stylesheets remotely and locally

I'm currently using Compass with Heroku using this configuration recommended on the Heroku knowledge base. Heroku has a read-only file system, and so the compiled stylesheets need to be stored in /tmp. This works fine remotely on Heroku; locally, however, Rails expects to find stylesheets in /public/stylesheets (when called through = stylesheet_link_tag 'screen.css', :media => 'screen, projection').
In order to solve the problem, I have created symbolic links in /public/stylesheets using ln -s tmp/stylesheets/screen.css public/stylesheets/screen.css and that seems to work.
Is there a way to solve this problem without using symbolic links, perhaps by changing some configuration in Rails? I've poked around without much success.
Here is my config/initializers/compass.rb:
require 'compass'
require 'compass/app_integration/rails'
Compass::AppIntegration::Rails.initialize!
# Required for Heroku:
require 'fileutils'
FileUtils.mkdir_p(Rails.root.join("tmp", "stylesheets"))
Compass::AppIntegration::Rails.initialize!
Rails.configuration.middleware.delete('Sass::Plugin::Rack')
Rails.configuration.middleware.insert_before('Rack::Sendfile', 'Sass::Plugin::Rack')
Rails.configuration.middleware.insert_before('Rack::Sendfile', 'Rack::Static',
:urls => ['/stylesheets'],
:root => "#{Rails.root}/tmp")
And here is my config/compass.rb:
project_type = :rails
project_path = Compass::AppIntegration::Rails.root
# Set this to the root of your project when deployed:
http_path = "/"
# Necessary for Heroku (original commented out:
css_dir = 'tmp/stylesheets'
#css_dir = "public/stylesheets/compiled"
sass_dir = 'app/views/stylesheets'
environment = Compass::AppIntegration::Rails.env
Any help would be greatly appreciated.
I was actually just about to set up Compass with our Rails application, which is hosted on Heroku, so cheers for giving me an excuse to work through this. :)
The answer is simple:
Modify 'config/compass.rb':
project_type = :rails
project_path = Compass::AppIntegration::Rails.root
http_path = "/"
environment = Compass::AppIntegration::Rails.env
if environment == 'production'
css_dir = "tmp/stylesheets"
sass_dir = "app/views/stylesheets"
else
css_dir = "public/stylesheets"
sass_dir = "app/stylesheets"
end
Then modify 'config/initializers/compass.rb':
require 'compass'
require 'compass/app_integration/rails'
Compass::AppIntegration::Rails.initialize!
require 'fileutils'
FileUtils.mkdir_p(Rails.root.join("tmp", "stylesheets"))
environment = Compass::AppIntegration::Rails.env
if environment == 'production'
Compass::AppIntegration::Rails.initialize!
Rails.configuration.middleware.delete('Sass::Plugin::Rack')
Rails.configuration.middleware.insert_before('Rack::Sendfile', 'Sass::Plugin::Rack')
Rails.configuration.middleware.insert_before('Rack::Sendfile', 'Rack::Static',
:urls => ['/stylesheets'],
:root => "#{Rails.root}/tmp")
end
... and voila, you're good.
ok, I'm a big heroku and compass fan myself so i've been through this many times
Heroku's docs, whilst giving correct information, provide poor advice in this instance.
When using compass, the best thing to do, 99.999% of the time is turn it off in production mode.
This means that you compile your stylesheets on your development machine and then add them to your git repo before pushing to heroku.
You will suffer a reasonably sizeable performance hit if you allow compass to compile on the server.
So here's what I do:
You should have a config.ru file at the base of your app. Open it and add the following:
require 'sass/plugin/rack'
use Sass::Plugin::Rack
Sass::Plugin.options[:never_update] = true
You can then remove quite a lot of the code from your initializer (especially the part where you unload Sass::Plugin::Rack). Additionally you will want to remove the if statement from compass.rb in config folder
Think about it, why would you want Sass to compile a stylesheet on the server? It just eats up processing power. Hope this helps,
EDIT::
PS - I should add that you will need to run compass watch from the command line now in order to get your stylesheets to compile in your dev environment
The recommended Heroku configuration will also work locally.
Removed the second 'Compass::AppIntegration::Rails.initialize!' from config/initializers/compass.rb, you only need it once.
Ensure your scss files are in 'app/views/stylesheets'
On both local and production servers the stylesheets will be compiled to tmp/stylesheets, and a request to /stylesheets will resolve to tmp/stylesheest. No need for two separate configurations.

Set up RSpec to test a gem (not Rails)

It is pretty easy with the added generator of rspec-rails to set up RSpec for testing a Rails application. But how about adding RSpec for testing a gem in development?
I am not using jeweler or such tools. I just used Bundler (bundle gem my_gem) to setup the structure for the new gem and edit the *.gemspec manually.
I also added s.add_development_dependency "rspec", ">= 2.0.0" to gemspec and did a bundle install.
Is there some nice tutorial what to do next to get RSpec working?
I've updated this answer to match current best practices:
Bundler supports gem development perfectly. If you are creating a gem, the only thing you need to have in your Gemfile is the following:
source "https://rubygems.org"
gemspec
This tells Bundler to look inside your gemspec file for the dependencies when you run bundle install.
Next up, make sure that RSpec is a development dependency of your gem. Edit the gemspec so it reads:
spec.add_development_dependency "rspec"
Next, create spec/spec_helper.rb and add something like:
require 'bundler/setup'
Bundler.setup
require 'your_gem_name' # and any other gems you need
RSpec.configure do |config|
# some (optional) config here
end
The first two lines tell Bundler to load only the gems inside your gemspec. When you install your own gem on your own machine, this will force your specs to use your current code, not the version you have installed separately.
Create a spec, for example spec/foobar_spec.rb:
require 'spec_helper'
describe Foobar do
pending "write it"
end
Optional: add a .rspec file for default options and put it in your gem's root path:
--color
--format documentation
Finally: run the specs:
$ rspec spec/foobar_spec.rb
Iain's solution above works great!
If you also want a Rakefile, this is all you need:
require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new(:spec)
# If you want to make this the default task
task default: :spec
Check the RDoc for RakeTask for various options that you can optionally pass into the task definition.
You can generate your new gem with rspec by running bundler gem --test=rspec my_gem. No additional Setup!
I always forget this. It's implemented here: https://github.com/bundler/bundler/blob/33d2f67d56fe8bf00b0189c26125d27527ef1516/lib/bundler/cli/gem.rb#L36
Here's a cheap and easy (though not officially recommended) way:
Make a dir in your gem's root called spec, put your specs in there. You probably already have rspec installed, but if you don't, just do a gem install rspec and forget Gemfiles and bundler.
Next, you'll make a spec, and you need to tell it where your app is, where your files are, and include the file you want to test (along with any dependencies it has):
# spec/awesome_gem/awesome.rb
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
$: << File.join(APP_ROOT, 'lib/awesome_gem') # so rspec knows where your file could be
require 'some_file_in_the_above_dir' # this loads the class you want to test
describe AwesomeGem::Awesome do
before do
#dog = AwesomeGem::Awesome.new(name: 'woofer!')
end
it 'should have a name' do
#dog.name.should eq 'woofer!'
end
context '#lick_things' do
it 'should return the dog\'s name in a string' do
#dog.lick_things.should include 'woofer!:'
end
end
end
Open up Terminal and run rspec:
~/awesome_gem $ rspec
..
Finished in 0.56 seconds
2 examples, 0 failures
If you want some .rspec options love, go make a .rspec file and put it in your gem's root path. Mine looks like this:
# .rspec
--format documentation --color --debug --fail-fast
Easy, fast, neat!
I like this because you don't have to add any dependencies to your project at all, and the whole thing remains very fast. bundle exec slows things down a little, which is what you'd have to do to make sure you're using the same version of rspec all the time. That 0.56 seconds it took to run two tests was 99% taken up by the time it took my computer to load up rspec. Running hundreds of specs should be extremely fast. The only issue you could run into that I'm aware of is if you change versions of rspec and the new version isn't backwards compatible with some function you used in your test, you might have to re-write some tests.
This is nice if you are doing one-off specs or have some good reason to NOT include rspec in your gemspec, however it's not very good for enabling sharing or enforcing compatibility.

Rails 3: how to load files in /lib?

I'm new to rails and making some sort of noob mistake: I frequently need to count the number of lines in a file, so I'm trying to monkey patch class File like this:
class File
def self.line_count( filename )
%x{wc -l #{filename}}.split.first.to_i
end
end
I saved this to /lib/file_util.rb. I thought that this was supposed to be auto-required, so that I could just use it, but that doesn't work:
$ rails console
>> File.line_count('Gemfile')
NoMethodError: undefined method `line_count' for File:Class
...
So I try to require it manually, no joy:
>> require '<myproj>/lib/file_util.rb' # same result with require 'file_util.rb'
=>nil
But it works if I require it within IRB:
$ irb
>> require '<myproj>/lib/file_util.rb'
=> true
>> File.line_count('Gemfile')
=> 22
I also tried to to add the require to config/application.rb:
...
Bundler.require(:default, Rails.env) if defined?(Bundler)
require 'file_util.rb'
module <myproj>
...
and I get:
$ rails console
<myproj>/config/application.rb:9:in `require': no such file to load -- file_util.rb (LoadError)
What am I doing wrong?
Ok, I seem to have mostly figured it out. Rails doesn't automatically require everything under /lib. It only auto loads when you try to use a new class name that matches a file name in lib. So if I define line_count in class FileUtil instead of File it automatically finds and loads 'file_util.rb'. But patching File and naming the patch file 'file.rb' doesn't work, since the File class is already defined, so Rails doesn't go looking for a definition.
My other problem was that I was trying to do the require too soon in the startup sequence, before Rails had a chance to enhance require to look in its directories. When I added "require 'file_util'" to config/environments/development.rb it works fine.
But this doesn't explain why I can't manually require the file from within rails console.
Monkeypatching classes can be done more easily by adding a file in config/initializers. All of those files are automatically loaded by Rails at startup.
You can call your initializer file anything you want. Try config/initializers/file.rb.