In this article we’ll consider four aspects of the Ruby on Rails 4 and Symfony 2 frameworks. When building commercial products with these frameworks, we have to extend their functionality with plugins, split applications into modules, and test and scale products. Thus, we will focus on how Rails 4 and Symfony 2 manage dependencies, modularity, testing, and scaling.
Bundler is itself a Ruby gem, but is the most common solution for installing other gems and managing dependencies between gems and Rails applications. Bundler downloads and installs required gems into your system from RubyGems.org and then manages dependencies among them.
If you work with several projects simultaneously, you must have separate Gemfiles for each project. Specify the gems you want to include in your app – and their dependencies – in the Gemfile, and then run the following command:
$ bundle install
This will automatically download and install the libraries you specified, and Bundler will trigger an alert if there are conflicts between libraries. For example, if a gem requires the latest version of another gem to work normally, Bundler will notify you. Ruby’s Bundler also provides other important commands, for example, a command to generate a new standard Gemfile for a project.
Composer is similar to Bundler, and is used with Symfony 2 and several other PHP frameworks. Composer’s creators were inspired by Ruby’s Bundler, so their approach to resolving dependencies in PHP projects is very similar. Specify required dependencies in the composer.json file and then run the following command:
php composer.phar install
Composer differs from Bundler in a very important respect, though, bringing PHP libraries and Symfony bundles into a specific project, not into the entire system. This helps define independent environments for particular projects. With Bundler, you may need to install several dozen gems that aren’t used in one project but are necessary for others. This usually leads to conflicts, which you have to resolve by removing old dependencies, by using similar libraries or by implementing monkey patches.
In short, Composer and Bundler are both great tools that can update libraries and handle other package-related operations with ease. That’s all you need when resolving dependencies in your projects!
Both Ruby on Rails 4 and Symfony 2 allow you to architect web apps from multiple components. This helps you build apps more quickly and replace irrelevant components more easily than if all your code is in one place. Components in Ruby on Rails are called engines, while Symfony calls them bundles.
You can think of engines as ordinary Rails plugins, although engines are more like small applications (read: more advanced than plugins). But in most respects, Rails plugins and engines have similar structures, such as the lib directory structure, and share common features.
When building a Rails app, you can take advantage of many existing engines. Here’s the top four Rails engines that we use at RubyGarage:
- Spree, an ecommerce engine for delivering web storefronts;
- Devise, an engine providing authentication for its parent applications;
- Forem, an engine for forum functionality;
- RefineryCMS, a Content Management System engine.
Many Rails engines and gems are located in the aforementioned RubyGems.org catalog, which currently offers more than 7,700 libraries.
Bundles in Symfony are also like ordinary plugins. When creating new bundles or using ready solutions in your Symfony 2 application, you have to encapsulate them using namespaces. Just add a bundle class to a namespace like this: namespace MyProject\MyFirstBundle;. This way you avoid irritating conflicts between bundles with identical names.
One of the biggest Symfony 2 bundle sources is KnpBundles.com, which currently offers almost 3,000 third-party solutions. In addition to KnpBundles, you can import any third-party PHP library to your web app from Packagist.org, which offers over 94,000 bundles.
Here’s a list of our favorite Symfony bundles that we install in almost every project:
The bottom line is that both the Ruby on Rails and Symfony 2 frameworks allow you to create component-based applications. Many developers have already built tons of libraries to help you solve common development tasks. So if you’re looking for standard functionality, chances are that you’ll find a suitable extension for your Rails or Symfony app.
To avoid regressions (when new bugs appear after implementing new functionality) and to find existing bugs as early as possible, you have to cover your code with automated tests. If bugs are found during the production stage, this usually demands much more time and effort to fix them than if they were found when the code was initially written.
Both Ruby on Rails and Symfony can benefit from the Test Pyramid – a concept by Mike Cohn – that defines the following priority for tests:
Unit tests are easier to implement and harder to break, so use them as much as is practical. These tests check an application’s business logic code. Functional tests check how individual software modules act on their own. Acceptance tests are the most time-consuming and unreliable, requiring you to compile and check complete application builds. Therefore, it’s better to cut down on the number of acceptance tests as much as possible.
Ruby on Rails offers a built-in framework for testing called Minitest. However, the majority of Ruby developers rely on RSpec. RSpec boasts great code readability and a large community that supports many additional extensions.
Here’s an example of a unit test written in RSpec:
We can also use RSpec to write functional tests that check if Rails controllers call required services correctly and respond with necessary data. Functional tests are especially important when you need to check controllers that are APIs for other clients (such as a mobile app).
Acceptance tests for Rails check an application’s functionality from a user’s point of view. For instance, a test can fill in a registration form, submit the form, and check the response against an expected result. We use RSpec to write a scenario, and then Poltergeist or a Webkit web driver to run an acceptance test in a browser.
You can also use the Gherkin language to write such test scenarios. Gherkin allows you to literally turn functionality requirements into Gherkin code and use them as test cases, usually written by a project manager.
Check out the following code to see an example scenario written in Gherkin:
Scenario: Post article by user
Given I signed up as user
When I write article "Justin Bieber"
And text of article is "is awesome"
And I post article
Then I should see "You have no access to post articles"
Now let’s talk about testing with Symfony 2. Symfony doesn’t come with its own testing framework. Instead, this PHP framework benefits from a third-party solution called PHPUnit, and even offers an official testing guide based on this solution.
PHPUnit is a PHP testing framework introduced in 2004. Later on, the Codeception framework was also introduced on top of PHPUnit to help support the behavior-driven development approach, when an application’s code is covered with tests even before it’s written.
The following code excerpt is a unit test written for PHPUnit:
You can find other testing code on Symfony/Testing to learn more details.
Acceptance tests for Symfony 2 can be performed with Selenium Server and Selenium WebDriver, which is managed by PHPUnit tests with the help of the php-webdriver library.
At RubyGarage, we regularly practice the Continuous Integration approach with Ruby on Rails 4 and Symfony 2. Continuous Integration means that new code is integrated into the repository several times a day and is verified by automated tests – and then manual code review – before going into a production build.
We should note that a framework’s scalability rarely correlates with an application’s ability to scale. In this section, we will review the practices you have to use in order to build scalable solutions with both Symfony and Ruby on Rails.
You can scale Rails 4 and Symfony 2 apps either vertically (by increasing server performance) or horizontally (by increasing the number of servers: application servers, database servers, caching servers, tasks servers, or CDN servers).
Web developers usually apply a complex of infrastructure improvements and configuration modifications to launch an application on distributed nodes. Here’s a general list of solutions and tools that we frequently use to scale Symfony 2 and Rails 4 apps:
- A three-tier client-server architecture (instead of a single-tier architecture);
- Redis for storing user sessions, DQL cache, and Doctrine mapping;
- MongoDB and ElasticSearch for storing aggregates of data;
- RabbitMQ to perform asynchronous operations through queues;
- Varnish to cache static resources and low dynamics HTML pages;
- Monolog and ELK stack for incident analytics;
- Hosting app nodes on Amazon Web Services (AWS);
- Assetic Bundle to minify and obfuscate JS and CSS files.
If we use all the solutions just listed, we can make Symfony 2 apps run over one billion requests per week with 30ms response time.
We would like to add that, although many Ruby on Rails applications have a monolithic structure, it’s easy to find a bottleneck in an app and implement that part as a standalone service. You can then further scale this service, which is a better option than increasing the number of app instances running simultaneously. This very same approach to scaling an application – which is called microservices – can be used with Symfony 2.
When using microservices, you can also implement some business logic in a lower-level language such as Go, Java, Scala, or C++. This is useful when you need to run unusually complex computations. It’s easy to integrate such a solution into your Ruby on Rails or Symfony application.
Hosting your Rails or Symfony application on Amazon Web Services (AWS) is one of the best options for scaling apps – as they’ll scale automatically. That is, additional servers will be provisioned to increase an app’s performance when required.
Ultimately, the scalability of an app built with either Symfony 2 or Ruby on Rails 4 is dependent on the app architecture you’ve engineered. Build a scalable architecture with either of these frameworks from the very beginning, and the app will scale in the future when necessary. (Note that making an architecture scalable means making smart decisions about how to link different app modules.)
As you can see, although these two frameworks – Ruby on Rails 4 and Symfony 2 – are written in completely different languages, they share very similar approaches to dependency management and modularity. Both offer advanced testing tools and allow almost endless scaling potential as long as your application is well architected. Because these frameworks are equally capable, the choice of which to use is really a matter of your personal preference.