📜 ⬆️ ⬇️

Merging Rails and Merb: A Year Later (Part 1 of 6)

Six consecutive articles on the merger of Rails and Merb were published on www.engineyard.com from December 2009 to April 2010. This is the first one.

Today, exactly one year since the day we announced the merger of Rails and Merb. Then there were a lot of skepticism about the success of this enterprise. The most common association for those who heard about our plans was the unicorn. On RailsConf last year, and DHH (David Heinemeyer Hansson, author of Rails. - approx. Transl. ), And I mentioned the unicorn in my reports, causing laughter regarding our high expectations and the inability to fulfill them for version 3.0.

A year has passed, it is time to reflect how well we have worked to achieve our goals. Over the next few days I will describe in detail the progress made towards each of the points in my original post ( http://www.engineyard.com/blog/2008/rails-and-merb-merge/ ).

I have already made several reports on these topics, so that some of you could already see separate things, but I also wanted to write it down for those who have not had time. I also added information previously omitted because of its complexity for the oral presentation, and too new to be told somewhere.
')

Modularity


Rails will become more modular, starting with the implementation of the kernel itself, including the ability to turn individual components on or off at will. We will focus on reducing the repetition of code inside Rails to make it possible to replace parts of Rails without interfering with other parts. This is what the vaunted “modularity” of Merb is.

We spent a lot of time at this stage, which brought us really a lot of fruit. I will give some typical examples.

ActiveSupport

First, we walked through ActiveSupport, making it adapted to choosing the right items. This means that the use of inflector (translation of words from the singular to the plural and vice versa), time extensions, class extensions, and everything else is now possible without independent study of the dependency graph. Here is what I mean (using the example of the to_sentence method in ActiveSupport from Rails 2.3):
Copy Source | Copy HTML<br/> module ActiveSupport<br/> module CoreExtensions<br/> module Array <br/> module Conversions<br/> def to_sentence (options = {})<br/> ...<br/> options.assert_valid_keys :words_connector, :two_words_connector, :last_word_connector, :locale<br/> ...<br/> end <br/> ...<br/> end <br/> end <br/> end <br/>

As you can see, there is a call to assert_valid_keys in another module in to_sentence , which means that to connect only active_support/core_ext/array/conversions , you would have to go through the file, find all implicit dependencies, and connect the corresponding modules separately. And of course, the structure of these dependencies could easily change in a future version of Rails, so it would be unsafe to hope that there will be a result. In Rails 3, the same file starts with:
Copy Source | Copy HTML<br/> require 'active_support/core_ext/hash/keys' <br/> require 'active_support/core_ext/hash/reverse_merge' <br/> require 'active_support/inflector' <br/>

This is because we ourselves have walked through the entire ActiveSupport library, found implicit dependencies and made them explicit. As a result, you can pull out the specific libraries you need for a small project, and not the entire ActiveSupport.

Better yet, parts of Rails now explicitly declare their dependencies on ActiveSupport. For example, the code that adds a log entry for ActionController received the following lines at the beginning:
Copy Source | Copy HTML<br/> require 'active_support/core_ext/logger' <br/> require 'active_support/benchmarkable' <br/>

This means that all parts of Rails now know exactly which parts of ActiveSupport they need. For simplicity, Rails 3 comes with a full set of ActiveSupport modules, so you can use things like 3.days or 3.kilobytes without problems. At the same time, if you want more control over exactly which modules are connected to the application, it is possible. You can declare config.active_support.bare = true in the configuration, and only those parts of ActiveSupport that are explicitly specified in the project files are included. You still need to include different trinkets if you want to use them - 3.days will not work right out of the box with the bare flag turned on.

ActionController

Another area in need of processing is ActionController. Previously, ActionController contained a lot of radically different elements within it. On closer examination, we found that these were actually three different components.

First, the functionality of the dispatcher. It included the controller itself, routing, middleware and rack extensions. Secondly, it contained a large amount of controller code intended to be used elsewhere, and actually used again in ActionMailer. Finally, there was code that served as an intermediary between the two, managing requests and responses through the entire controller architecture.

In Rails 3, each of these components is separated from the other. The dispatcher's functionality was moved to ActionDispatch, its code is tight and really turned into a conceptual component. Parts of ActionController, intended for use by non-HTTP controllers, were moved to a new component called AbstractController, from which both ActionController and ActionMailer inherit.

Finally, ActionController itself underwent a thorough renovation. In fact, we isolated each individual component and made it possible to start with a minimum and supplement it with components of your choice. Our old friend ActionController::Base just starts from the same place and adds everything you need. For example, look at the beginning of a new version of this class:
Copy Source | Copy HTML<br/> module ActionController<br/> class Base < Metal<br/> abstract!<br/> include AbstractController::Callbacks<br/> include AbstractController:: Logger <br/> include ActionController::Helpers<br/> include ActionController::HideActions<br/> include ActionController::UrlFor<br/> include ActionController::Redirecting<br/> include ActionController::Rendering<br/> include ActionController::Renderers::All<br/> include ActionController::Layouts<br/> include ActionController::ConditionalGet<br/> include ActionController::RackDelegation<br/> include ActionController:: Logger <br/> include ActionController::Benchmarking<br/> include ActionController::Configuration <br/>

All we are doing here is adding all the available modules, so the initial way of using Rails is the same as before. The real strength of what we have done here is the same as in ActiveSupport: each module declares its dependencies on other modules, so you can turn on, for example, Rendering without having to figure out which other modules should be connected and what order.

Here is a fully working Rails 3 controller:
Copy Source | Copy HTML<br/> class FasterController < ActionController::Metal<br/> abstract!<br/> # Rendering , <br/> # <br/> include ActionController::Rendering <br/> include ActionController::Layouts<br/> append_view_path Rails.root.join( "app/views" ) <br/> end <br/> <br/> class AwesomeController < FasterController <br/> def index <br/> render "_" <br/> end <br/> end <br/>

And further in the routing file with complete success can be done:
Copy Source | Copy HTML<br/>MyApp.routes.draw do <br/> match "/must_be_fast" , :to => "awesome#index" <br/> end <br/>

In essence, ActionController::Base was just one of the ways to create controllers. This is the same as the classic Rails, but with the ability to make your own, if the original is not to your taste. It's really easy to match your requirements: if you want to add the before_filter functionality to FasterController , you can simply add AbstractController::Callbacks .

Notice that adding these modules automatically added AbstractController::Rendering (the rendering functionality is shared with ActionMailer), AbstractController::Layouts and ActiveSupport::Callbacks .

This makes it easy to add only the functionality you need in performance-sensitive places without having to use a completely different API. If additional functionality is required, you can easily add additional modules or eventually use the full ActionController::Base without having to ActionController::Base something along the way.

This is, in fact, the idea of ​​the core of Rails 3: there are no monolithic components, only modules without complex relationships, running by default in large packages. This allows people to continue to use Rails in the same way as in previous versions, but it supports the code with alternative options. There is no more functionality enclosed in inaccessible forms.

The immediate benefit of all this is that ActionMailer deliberately gets all the functionality of ActionController in a simple way. Everything, starting with layouts and helpers to filters, uses the same code used in ActionController, so ActionMailer can never slip from the functionality of ActionController (in the process of how ActionController will evolve).

Middleware also gets a helping hand. ActionController::Middleware , middleware with all the benefits of ActionController, allows you to add any ActionController features you like (as Rendering, ConditionalGet, Request and Response objects, etc.). Here is an example:
Copy Source | Copy HTML<br/> # <br/> class AddMyName < ActionController::Middleware<br/> def call (env)<br/> status, headers, body = @app. call (env)<br/> headers[ "X-Author" ] = "Yehuda Katz" <br/> headers[ "Content-Type" ] = "application/xml" <br/> <br/> etag = env[ "If-None-Match" ]<br/> key = ActiveSupport::Cache.expand_cache_key(body + "Yehuda Katz" )<br/> headers[ "ETag" ] = %[ "#{Digest::MD5.hexdigest(key)}" ]<br/> if headers[ "ETag" ] == etag<br/> headers[ "Cache-Control" = "public" ]<br/> return [ 304 , headers, [ " " ]]<br/> end <br/> <br/> return status, headers, body<br/> end <br/> end <br/> <br/>

Copy Source | Copy HTML<br/> # Rack <br/> class AddMyName < ActionController::Middleware<br/> include ActionController::RackDelegation<br/> <br/> def call (env)<br/> self .status, self .headers, self .response_body = @app. call (env)<br/> <br/> headers[ "X-Author" ] = "Yehuda Katz" <br/> <br/> # <br/> self .content_type = Mime ::XML # delegates to the response <br/> response.etag = "#{response.body}Yehuda Katz" <br/> response.cache_control[:public] = true <br/> <br/> self .status, self .response_body = 304 , nil if request.fresh?(response)<br/> <br/> response.to_a<br/> end <br/> end <br/>

Copy Source | Copy HTML<br/> # ConditionalGet <br/> class AddMyName < ActionController::Middleware<br/> # RackDelegation <br/> include ActionController::ConditionalGet<br/> <br/> def call (env)<br/> self .status, self .headers, self .response_body = @app. call (env)<br/> <br/> headers[ "X-Author" ] = "Yehuda Katz" <br/> <br/> self .content_type = Mime ::XML<br/> fresh_when :etag => "#{response.body}Yehuda Katz" , :public => true <br/> <br/> response.to_a<br/> end <br/> end <br/>

I think we really kept the promise to bring modularity into Rails. I think that the level of what happened in the new version exceeds the expectations of many a year ago, and this is exactly the territory of the golden unicorn. Enjoy!

Next time, I'll talk about improving performance in Rails 3. I hope it won't be too fast. :)

Source: https://habr.com/ru/post/87907/


All Articles