Skip to content

Instantly share code, notes, and snippets.

@wagnerjgoncalves
Last active April 28, 2016 00:43
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wagnerjgoncalves/3b12a6f4c9e92d8de74a to your computer and use it in GitHub Desktop.
Save wagnerjgoncalves/3b12a6f4c9e92d8de74a to your computer and use it in GitHub Desktop.
Notes from Crafiting Rails 4 Applications

Crafiting Rails 4 Applications

  • This pioneering book deep-dives into the Rails plugin APIs and shows you, the intermediate Rails developer, how to use them to write better web applications and make your day-to-day work with Rails more productive.

  • You’ll learn how the Rails rendering stack works and customize it to read templates from the database while you discover how to mimic Active Record behavior, such as validations, in any other object. You’ll find out how Rails integrates with Rack, the different ways to stream data from your web application, and how to mix Rails engines and Sinatra applications into your Rails apps, so you can choose the most appropriate tool for the job. In addition, you’ll improve your productivity by customizing generators and responders.

Cap 1 - Create your own renderer (AbstractController)

Rails uses the model-view-controller (MVC) architecture pattern to organize our code.

The common interface to render a given model or template is the render method. Besides knowing how to render a :template or a :file, Rails can render raw :text and a few formats, such as :xml, :json, and :js.

Explain about the structure of rails plugin.

Render is nothing more than a hook exposed bu the render method to customize its behavior.

Playing with Prawn: pdf library for Ruby.

ActionController::Renderers.add :pdf do |filename, options|
  pdf = Prawn::Document.new
  pdf.text render_to_string(options)

  send_data(pdf.render, filename: "#{filename}.pdf", disposition: "attachment")
end

AbstractController was designed in a way that developers can cherry-pick the funcionality they want.

Abstract Controller’s rendering stack is responsible for normalizing the arguments and options you provide and converting them to a hash of options that ActionView::Renderer#render accepts, which will take care of finally rendering the template.

An instance of ActionView::Renderer called view_renderer is created and the render method is called on it with two arguments the view_context and the hash of normalized options.

The view context is an instance of ActionView::Base it is the context in which our templates are evaluated.

Take a look Firgure 3. Visualization of the rendering stack

Cap 2 - Building Models with Active Model (Mail Form plugin)

ActiveModel was originally created to hold the behavior shared between ActiveRecord and ActiveResource. It`s also responsible for defining the API (Application Programming Interface) required by controllers and views.

  • Active Resource (ARes): Connects business objects and Representational State Transfer (REST) web services.

  • ActiveModel::AttributeMethods: is a module that tracks all defined attributes, allowing us to add a commom behavior to all of them dinamically. Provide a way to add suffix and prefix to your methods using attribute_method_prefix, attribute_method_suffix, define_attribute_methods and prefix_attribute or attribute_suffix methods definitions.

  • ActiveModel::Conversion: Handles default conversions:

    • #to_model: Rails controllers and view helpers manipulate the model using to_model instead of the model directly;
    • #to_key: return an array of keys that uniquely identifies the model;
    • #to_param: used in rounting;
    • #to_partial_path: is invoked every time we pass a record or a collection of records to render in our views;

    “This not only makes our code cleaner, but also improves our application performance”

  • ActiveModel::Naming: Creates a model_name to your model, acts like a String and provide a few methods, such as human, singular, and others that are inflected from the model name.

  • ActiveModel::Translation: Provides integration between your model and Rails Internationalization (i18n) framework.

  • ActiveModel::Validations: Provides a full validation framework to your objects.

    • acceptance
    • validates_associated
    • confirmation
    • exclusion
    • format
    • inclusion
    • length
    • numericality
    • presence
    • absence
    • uniqueness
    • validates_with
    • validates_each
def validates_presence_of( *attr_names )
  validates_with PresenceValidator, _merge_attributes(attr_names)
end
  • ActiveModel::Callbacks: Provide an interface for any class to have Active Record like callbacks.

  • ActiveModel::Dirty: Provides a way to track changes in your object in the same way as Active Record does.

  • ActiveModel::Serialization: Provides a basic serialization to a #serializable_hash for your objects.

Used Ruby autoload that allow us lazily load a constant when it is first referenced and a fast boot process, example:

module Example
  autoload :Base, "example/base"
end

Rails provides a module called ActiveModel::Lint::Tests that defines several tests asserting that each method required in an Active Model--compliant API exist.

Cap 3 - Retrieving View Templates from Custom Stores

Rails provides hooks that allow us to retriece templates from anywhere. Whenever we render a template in Rails, its source must first be compiled into executable Ruby code. Every time some Ruby code is executed, its execution happens inside a given context and, in a Rails application, views are executed inside the view context object.

The view renderer has access to an instance of ActionView::LookupContext usually referred to as lookup_context. The lookup context is shared between controllers and views. It`s also responsible for holding all view paths. A view path is a collection of objects able to find templates.

Rails provides a well-defined API for adding any object as a view path. This means we’re not forced to store view templates in the filesystem. Internally Rails calls them template resolvers (ActionView::Resolver) and they must comply with the Resolver API. The Resolver API is composed of a single method: find_all which should return an array of templates.

Cap 4 - Sending Multipart Emails Using Template Handlers

Handler is anything that responds to call and returns a String.

The template handler`s responsibility in the rendering stack is to compile a template to Ruby source code.

  ActionView::Template.register_template_handler(extension, handler)


  require "action_view/template"

  ActionView::Template.register_template_handler :rb, lambda { |template| template.source }

For an object to be compliant with de handler API it needs to respond to call method.

Rails generators provide hooks to allow other generatos to extend ans customize the generated code:

  • Controller generator: Erb::Generators::ControllerGenerator;
  • Mailer generator: Erb::Generators::MailerGenerator;
  • Scaffold generator: Erb::Generators::ScaffoldGenerator;

Erb::Generators::MailerGenerator inherits from Erb::Genearats::ControllerGenerator and use format :text.

Take a look Thor::Actions.

Rails::Railtie allow us to hook into Rails`s and configure some defaults.You should include a railtie in your plug-in only if at least one of the following is true:

  • Your plug-in needs to perform a given task while or after the Rails application is initialized;
  • Your plug-in needs to change a configuration value for instance, setting a generator;
  • Your plug-in must provide Rake tasks and generators in nondefaults locations;
  • Your plug-in wants to run custom code whenever the Rails console or the Rails runner is started;
  • You want your plug-in to provide configuration options to the applciation, config.my_plugin.key = :value;

Cap 5 - Streaming Server Events to Clients Asynchronously

Extending Rails with Engines

Rails::Engine is a Rails::Railtie with some default initializers and the Paths application programming interface (API). An Engine has several initializers that are responsible for booting the engine. Stream events using ActionController::Live. The streaming functionality doesn’t work in WEBrick, example using Puma.

Polling was the most common technique to receive updates from the server in the browser (a lot of requests generating a lot of over-head).

WebSockets: allow both the client and serve to exchange information over the same connection (new protocol) Server Sent Events (SSE): is a one way communication channel from the server to the client, and can be used with any web server that is able to stream responses.

Used the gem listen that exposes all major operation-system notifications mechanisms under a single, easy-to-use API.

Threads Queues

Code-loading techniques

Autoload techniques:

  • Rails`s autoloading = ActiveSupport::Dependencies
  • Ruby`s autoload thread-safe
  • Rails`s autoload isn’t thread-safe

Rails allow only one thread to run by default, which means it can serve only one request at a time.

config.allow_concurrency (our own risk)

Eager-Load Techniques:

Rails eager-load your code inside app folder on boot.

config.eager_load_namespaces #Keeps a list of namespaces to eager-load

Keep in mid that eager loading is not only beneficial for threaded web servers like Puma, but also for fork-based servers like Unicorn.

We guarantee that all our code code loaded up front and we don’t need to spend time autoloading the code on every request.

Cap 6 - Writing DRY Controllers with Responders

Scaffolding the only problem with scaffolding is that generated controllers are still a little bit verbose.

To avoid a lot of similar blocks respond_to at controllers, Rails provide respond_with that uses ActionController::Responder to abstract how controllers respond.

We need to understand the three variables that affect how controllers respond: request type, HTTP verb and resource status.

Navigational and API Requests: HTML and JavaScript Object Notation (JSON).

Anything that responds to call and accept three arguments (controller, resource and hash options) can be a responder.

navigation_behavior (html) and api_behavior (xml, json)

The great advantage in using ActionController::Responder is that it centralizes how our application should behave per format.

Set a Responder:

ApplicationController.responder = MyAppResponder

or custom responder per controller:

class MyController < ApplicationController
  self.responder = MyCustomResponder
end

Implemented a Flash Responder (Responders::Flash)

Implemented a HTTP Cache Responder (Responders::HttpCache)

Rails provides several helpers on top of HTTP cache specification:

response.last_modified

request.fresh?(response)

head

get?

ActionController::Base.perform_caching

Customize Generators Responders::Generators::InstallGenerator

The controller generated by scaffolding should change depending on the ORM. Rails solves this problem by creating an object responsible for telling the scaffold generator how the interaction with the ORM happens (Rails::Generators::ActiveModel).

Cap 7 - Managing Application Events with Mountable Engines

Rails provides a centralized way to publish and subscribe to events happening inside an application with the ActiveSupport::Notifications application programming interface (API).

Used this API to subscribe to all actions processes by application ans store them in MongoDB database.

Mountable and isolated engines:

A mountable engine uses its own router instead of adding routes directly to the application router. An isolated engine is built inside its own namespace, with its own models, controllers, views, assets and helpers.

rails plugin new my_plugin --mountable

Routes:

Rails.application.routes.draw do
  mount MyPlugin::Engine => 'my_plugin'

Isolated namespace:

module MyPlugin
  class Engine < ::Rails::Engine
    isolate_namespace MyPLugin
  end
end

The Notifications API consists of just two methods: instrument and subscribe

ActiveSupport::Notifications.instrument("process_action.action_controller", format: :html, path: "/", action: "index") do
  process_action("index")
end

ActiveSupport::Notifications.subscribe("process_action.action_controller") do |*args|
  # do something
end

args:

  • name
  • started_at
  • ended_at
  • instrumenter_id
  • payload

Rails and Rack

Rack provides a minimal, modular, and adaptable interface for developing web applications in Ruby.

We can easily mount a Sinatra application inside the Rails router, similar to how we mounted an engine inside the dummy application.

A Rack application is any Ruby object that responds to call. It takes exactly one argument, the environment, and returns an array of exactly three values: the status, the headers, and the body:

# config.ru
# $ rackup -s puma

require 'rack'

class StreamingRack
  def call(env)
    [200, { 'Content-Type' => 'text/html' }, self]
  end

  def each
    while true
      yield 'Helo Rack!\n'
    end
  end
end

run StreamingRack.new

Whenever you call get, post, put, delete, resources, or resource in the router domain-specific language, those methods invoke to the match method. The only method that has different semantics is mount, used in the dummy application to mount our engine.

Rack provides the concept of middleware, which allows us to add custom code between those Rack applications, giving us even more flexibility.

A middleware wraps around a Rack application. It lets us manipulate both the request sent down to the application and the response the application returns. Any request to an action in a Rails controller passes through three middleware stacks.

Rails::Rack::LogTailer Rails::Rack::Debugger

We can add midlleware to a controller:

class MyController < ApplicationController
 use MyMiddleware
end

Cap 8 - Translating Applications Using Key-Value Back Ends

I18n framework comes with diferrent back ends that allow us to store translations in places other tahn YAML files. On the downside, retrieving translations from the database instead of an in-memory hash has a huge impact on performance.

Rails::Application specific behavior:

  • An application is responsible for all bootstrapping;
  • An application has its own router and middleware stack;
  • An application should load an initialize all plug-ins;
  • An application is responsible for reloading code and routes between requests if the changed;
  • An application is responsible for loading tasks and generators when appropriate;

All initializers available in a Rails application:

Rails.application.initializers.map(&:name)

Some routes may be defined during initialization or inside a file that is nerver reloaded, so Rails provide routes.prependa and routes.append to define sticky routes.

The I18n framework ships with three different back ends:

  • I18n::Backend::Simple: Keeps translations in an in-memory hash populated from YAML files;
  • I18n::Backend::KeyValue: Uses any key-value store as a back end;
  • I18n::Backend::Chain: Allows you to chain several back ends; in other words, if a translation cannot be found in one back end, it searches for it in the next back end in the chain;

I18n Transliteration support:

I18n.transliterate("dziękuję") # => "dziekuje"

Extensions provided by the I18n library:

  • I18n::Backend::Cache
  • I18n::Backend::Cascade
  • I18n::Backend::Fallbacks
  • I18n::Backend::Gettext
  • I18n::Backend::InterpolationCompiler
  • I18n::Backend::Memoize
  • I18n::Backend::Metadata
  • I18n::Backend::Pluralization
  • I18n::Backend::Transliterator

Integrate Rails + Sinatra + Haml template markup (Mount Sinatra app inside Rails application with specific route)

Add Devise a full-stack authentication solution based on Rack, and take a deeper look at how we can use Capybara to test Rack applications.

Add authentication to Sinattra using a proxy Warden middleware:

env["warden"].authenticate!(scope: "admin")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment