Creating a Component-Based Application with Rails
- 2.1 The Entire App Inside a Component
- 2.2 ActiveRecord and Handling Migrations within Components
- 2.3 Handling Dependencies within Components
This chapter excerpt tells the story of creating a full Rails application within a component. From the first steps to migrations and dependency management, it covers the common pitfalls of the unavoidable aspects of component-based Rails.
Save 35% off the list price* of the related book or multi-format eBook (EPUB + MOBI + PDF) with discount code ARTICLE.
* See informit.com/terms
Photo: mattiaath/123RF
In preparation for creating your first CBRA app, you should ensure that your system is up to date with regard to Ruby and Rails. I suggest using the latest published versions of both Ruby and the Rails gems. The way I have my system set up, using the Ruby Version Manager (RVM) (http://rvm.io/), is something like the following:
Install rvm, bundler, and rails. Execute anywhere
$ rvm get stable $ rvm install 2.4.2 $ gem install bundler -v '1.15.4' $ gem install rails -v '5.1.4'
2.1 The Entire App Inside a Component
In this section, we will be creating a Rails application that contains no handwritten code directly in the app folder and instead has all code inside an engine mounted into the application.
The first step in creating a CBRA app is the same as creating any new Rails app: rails new!
Generate Sportsball app. Execute where you like to store your Rails projects
$ rails new sportsball $ cd sportsball
I often refer to this Rails app as the main app or container app. However, we are not going to bother writing any code of our application into the newly created folder structure. Instead, we are going to write all of it inside a component. That component will be implemented through a Rails engine. In fact, let us force ourselves to stick to this and delete the app folder.
Delete the app folder. Execute in ./
$ rm -rf app
There is no obvious candidate as to where the components of our application should be added in the created folder structure. All folders have their specific purpose and our components don’t really fit in any of them. Consequently, I believe components should not go into the created folder structure and instead should find their place in a new directory under the root. I like to use components as the folder name.
Create the components folder. Execute in ./
$ mkdir components
To create our first component, we will use the rails plugin new command to create a Rails engine. For lack of a better name, we will call this first component app_component.
Initially, I will use app_component as a component name. This is despite what I said before about the naming of components. Read the following for a defense. But first, we generate this component using the following command:
Generate the app_component gem. Execute in ./
$ rails plugin new components/app_component --full --mountable
The command line parameters --full and --mountable make the plugin generator create a gem that loads a Rails::Engine that isolates itself from the Rails application. It comes with a test harness application and blueprints for tests based on Test::Unit. See Appendix A for a full explanation of these creation parameters and to learn how to switch the tests to RSpec.
We can now cd into the component folder in ./components/app_component and execute bundle. Depending on our version of bundler, there will be a warning (in older versions) or error about our gemspec not being valid:
app_component gem error during bundle. Execute in ./components/app_component
$ bundle The latest bundler is 1.16.0.pre.3, but you are currently running 1.15.4. To update, run `gem install bundler --pre` You have one or more invalid gemspecs that need to be fixed. The gemspec at ./components/app_component/app_component.gemspec is not valid. Please fix this gemspec. The validation error was '"FIXME" or "TODO" is not a description'
This warning is because there are a bunch of “TODO” entries in the gemspec that are picked up on by bundler and that we need to remove. If you fill out all the fields with “TODO” in the Gem::Specification block of the gemspec, this warning/error will go away. However, email, homepage, description, and license are not required, and if you delete those you only need to fill in authors and summary to get rid of the warning.
Removing all TODOs from the generated gemspec leads to the following file, which will not throw an error:
./components/app_component/app_component.gemspec
1 $:.push File.expand_path("../lib", ____FILE____ ) 2 3 # Maintain your gems version: 4 require "app_component/version" 5 6 # Describe your gem and declare its dependencies: 7 Gem::Specification.new do |s| 8 s.name = "app_component" 9 s.version = AppComponent::VERSION 10 s.authors = ["Stephan Hagemann"] 11 s.summary = "Summary of AppComponent." 12 s.license = "MIT" 13 14 s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md"] 15 16 s.add_dependency "rails", "~> 5.1.0" 17 18 s.add_development_dependency "sqlite3" 19 end
If we now go back up into the root folder of Sportsball and call bundle there, we notice that among the many gems that are being used, we also see our new component app_component.
Seeing AppComponent being used in the app (some results omitted). Execute in ./
$ bundle The latest bundler is 1.16.0.pre.3, but you are currently running 1.15.4. To update, run `gem install bundler --pre` Resolving dependencies... Using rake 12.2.1 . . . Using rails 5.1.4 Using sass-rails 5.0.6 Using app_component 0.1.0 from source at `components/app_component` Bundle complete! 17 Gemfile dependencies, 73 gems now installed. Use `bundle info [gemname]` to see where a bundled gem is installed.
This is because rails plugin new not only creates a new gem, it also automatically adds a reference to this gem into the Gemfile of an app in the context of which it was created. That is, because we executed rails plugin new from the root of the Sportsball app, we got also got a new entry in our Gemfile.
./Gemfile showing reference to AppComponent gem (some lines omitted)
1 source 'https://rubygems.org' 2 3 . . . 4 5 gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] 6 gem 'app_component', path: 'components/app_component'
In earlier versions of Rails, this does not happen automatically. If you are in this situation, add the last line from the previous snippet to your Gemfile to get the same result when bundling the app. Notice that the gem reference uses the path option to specify where this gem can be found (namely, our local filesystem). Commonly, path is used only for gems under development, but as we will see, it works just fine for use in CBRA applications.
Back to AppComponent. While it is now hooked up within the Sportsball application, it does not actually do anything yet. Let us fix that next. We will create a landing page and route the root of the engine to it.
Generate a controller for the component. Execute in ./
$ cd components/app_component $ rails g controller welcome index
By moving into the folder of the gem, we are using the engine’s Rails setup. Because of that and because we made this a mountable engine, our welcome controller is created inside the AppComponent namespace. See Appendix B for an in-depth discussion of the folder structure and the namespace created by the engine.
We change the component’s routes as follows to hook the new controller up to the root of the engine’s routes:
./components/app_component/config/routes.rb
1 AppComponent::Engine.routes.draw do 2 root to: "welcome#index" 3 end
Last, we make the main app aware of the routes of the engine by mounting them into the routes of the application. Here, we are mounting the engine to the root of the app as there are no other engines and the main app will never have any routes of its own. See Appendix B for a discussion of how routing works and what options you have.
./config/routes.rb
1 Rails.application.routes.draw do 2 mount AppComponent::Engine, at: "/" 3 end
That’s all. Let’s start up our server!
Start up the Rails server. Execute in ./
$ rails s => Booting Puma => Rails 5.1.4 application starting in development => Run `rails server -h` for more startup options Puma starting in single mode... * Version 3.10.0 (ruby 2.4.2-p198), codename: Russell's Teapot * Min threads: 5, max threads: 5 * Environment: development * Listening on tcp://0.0.0.0:3000 Use Ctrl-C to stop
Now, when you open http://localhost:3000 with a browser, you should see (as in Figure 2.1) your new welcome controller’s index page. That wasn’t too bad, was it?
Figure 2.1. Your first CBRA-run web page
Having the component separated from the container application also allows us to draw a component diagram (see Figure 2.2). This diagram shows our component in the middle using its gem name. The surrounding box with the application name indicates the Rails application of which our code is a part. The arrow indicates that the container app is dependent directly on app_component (as we just saw, AppComponent was added directly to the Gemfile of Sportsball). In subsequent sections of the book we will see how we create a web of components with dependencies among them.
Figure 2.2. Our first component diagram
Generating this graph yourself at this stage of the app is a bit tricky. It is the way we are referencing the component in Gemfile currently. If you still want to do it, there are a couple of steps involved.
First, you will need to install the cobradeps gem (https://github.com/shageman/cobradeps), a gem I wrote to allow for the generation of Rails component diagrams. This gem depends on graphviz (http://www.graphviz.org/), an open-source graph visualization software. You install these two applications as follows (assuming that you are on OSX and are homebrew, which I recommend you do: https://brew.sh/).
Install graphviz and cobradeps. Execute anywhere
$ brew install graphviz $ gem install cobradeps
To alleviate the gem reference problem we mentioned, change the AppComponent line in your Gemfile to this:
Reference to app_component in ./Gemfile allowing for graph generation
1 gem 'app_component', 2 path: 'components/app_component', 3 group: [:default, :direct]
The group: [:default, :direct] will behave normally with bundler and is used by cobradeps to determine that the gem is indeed a direct dependency (we will see in Section 2.3 why this is necessary). Now you can generate the component graph by executing the following statement. The result will be output into ./component_diagram.png.
Generate a component graph for Sportsball. Execute in ./
$ cobradeps -g component_diagram .
That’s it! Your first CBRA app is ready to get serious. You can now continue all feature development inside of components/app_component.
Some aspects of what we glossed over in this section are covered in more depth in the Appendixes. Refer to Appendix A for an in-depth look at the various kinds of engines that Rails features, and Appendix B for an introduction to engine routing and mounting.