Active-support: Outside & Inside

Research of rails standard library ActiveSupport. Realization review and some useful functions.


“It’s best to have your tools with you. If you don’t, you’re apt to find something you didn’t expect and get discouraged.” Stephen King, On Writing: A Memoir of the Craft

Introduction

All developers who build Rails applications use Active-Support. It extends Rails and every programmer has probably used it at one time:

... rails/rails.gemspec



  21  s.files = ["README.md"]

  22

  23  s.add_dependency "activesupport", version

  24  s.add_dependency "actionpack",    version

  25  s.add_dependency "actionview",    version

But often, many do not realize when they use this gem. In this article, I want to recall some useful features that it brings to our Rails applications.

Gem

Active Support is a collection of extensions for standard Ruby classes. Mainly they are targeted at working with the Web. But it also has many other useful extensions that may come in handy in developing non-web applications.

Often when we generate Rails app most of the cool stuff is already available to us, because Active Support adds its extensions to standard Ruby classes. All changes, to which standard classes are exposed, can be viewed here.

https://github.com/rails/rails/tree/master/activesupport/lib/active_support/

Let’s see these changes in detail.

What we already had

Active Support contributes to converting objects to strings. Instead of the basic Ruby notation with ‘e’, we get a floating-point number.

irb

  > require "bigdecimal"

  => true

  > BigDecimal.new("0.2").to_s

  => "0.2e0"

  rails c

  > BigDecimal.new("0.2").to_s

  => "0.2"

Range class was also changed. Range.to_s method assumes the argument in one of the formats. Now there is only one available format - :db.

> (Date.today..Date.tomorrow).to_s

=> "2018-04-27..2018-04-28"

> (Date.today..Date.tomorrow).to_s(:db)

=> "BETWEEN '2018-04-27' AND '2018-04-28'"

The module developers also paid attention to Range.include?method by adding the ability to pass a different range as an argument. Thereby they check nesting of one range in another (by entry of interval end-points into recipient interval)


ruby > (Date.yesterday..Date.tomorrow).include?((Date.today..Date.tomorrow) ) => true

We are all aware of Array.slice method. Active Support also adds this function in Hash.

It works the same as for Array, but uses keys instead of indexes.


ruby > [1,2,3,4].slice((1..2)) => [2, 3] > {a: 1, b: 2, c: 3}.slice(:a, :c) => {:a=>1, :c=>3}

What we got used to

Mostly we use present? and blank? methods. And yes, these methods are not in standard Ruby collection. These methods are provided in our Rail application by Active Support.

    #...rails/activesupport/lib/active_support/core_ext/object/blank.rb



    19 def blank?

    20  respond_to?(:empty?) ? !!empty? : !self

    21 end



    26 def present?

    27  !blank?

    28 end

As you can see from the source code, this is a test for empty? (Method from Object).

Active Support also defines the behavior for the following basic class methods:

NilClass, FalseClass, TrueClass, Array, Hash, String, Numeric, Time.

Another method is symbolize_keys.It uses a more extended version of working with keys in

Hash: transform_keys

    25 def transform_keys!

    26   return enum_for(:transform_keys!) { size } unless block_given?

    27   keys.each do |key|

    28     self[yield(key)] = delete(key)

    29   end

    30   self

    31 end unless method_defined? :transform_keys!

As you can see from the code, we create a new key by running the block over the old key, simultaneously getting the value from the old key and deleting it. Simple and elegant!

A wonderful drapper gem, that is used to implement decorator design pattern, actively uses the

delegate functionality.

    #draper/draper.gemspec

    20 s.add_dependency 'activesupport', '~> 5.0'



   # ... rails/activesupport/lib/active_support/core_ext/module/delegation.rb



    157  def delegate(*methods, to: nil, prefix: nil, allow_nil: nil)

    158    unless to

We should mention that this extension does not use standard Module::Forwardable.

Thanks to Active Support there is a “pseudo object functionality” in Rails (something like Hashie::Mash). There is no magic in Rails configuration - only Hash:

    config.active_storage = ActiveSupport::OrderedOptions.new

    ...

    config.action_mailer = ActiveSupport::OrderedOptions.new

    ...

    config.active_job = ActiveSupport::OrderedOptions.new

    ...

    config.i18n = ActiveSupport::OrderedOptions.new

    ...

    config.i18n.fallbacks = ActiveSupport::OrderedOptions.new

    ...

    config.action_controller = ActiveSupport::OrderedOptions.new

    ...

    config.active_support = ActiveSupport::OrderedOptions.new

    ...

    config.active_record = ActiveSupport::OrderedOptions.new

    ...

    #https://github.com/rails/rails/blob/master/railties/lib/rails/application/configuration.rb

    def method_missing(method, *args)

      if method =~ /=$/

        @configurations[$`.to_sym] = args.first

      else

        @configurations.fetch(method) {

          @configurations[method] = ActiveSupport::OrderedOptions.new

        }

      end

    end

Only ActiveSupport::OrderedOptions and a bit of metaprogramming.

Another perk of Active Support is try method.

    7  def try(*a, &b)

    8    try!(*a, &b) if a.empty? || respond_to?(a.first)

    9  end

    10

    11  def try!(*a, &b)

    12    if a.empty? && block_given?

    13      if b.arity == 0

    14        instance_eval(&b)

    15      else

    16        yield self

    17      end

    18    else

    19      public_send(*a, &b)

    20    end

    21  end

There is nothing difficult about that, either. For any of you, just like me, who were unaware that arity determines the quantitative and qualitative set of arguments that the method returns:

    class C

      def one;    end

      def two(a); end

      def three(*a);  end

      def four(a, b); end

      def five(a, b, *c);    end

      def six(a, b, *c, &d); end

    end

    c = C.new

    c.method(:one).arity     #=> 0

    c.method(:two).arity     #=> 1

    c.method(:three).arity   #=> -1

    c.method(:four).arity    #=> 2

    c.method(:five).arity    #=> -3

    c.method(:six).arity     #=> -3

  # ©https://apidock.com/ruby/Method/arity

Another common example from Active Support is the in?predicate. predicate. And yes, Ruby can only respond to include?. Also Ruby does not know such a method as the parent of the module / class. In any confusing situation it will return to Object.

    13 parent_name = name =~ /::[^:]+\Z/ ? $```.freeze : nil

    ...

    35  parent_name ? ActiveSupport::Inflector.constantize(parent_name) : Object

And Ruby does not know about subclasses (subclasses method). To get this information, you have to turn upside down the entire ObjectSpace and that is a lot of resources:

    #irb

    > ObjectSpace.each_object(Class){|k| p k.name}

    .....

    => 510



   #rails c

    > ObjectSpace.each_object(Class){|k| p k.name}

    ..........................(Beware – a very long output)

    => 15795

Did you know that in Rails, you can define the attributes of a class (not instance)? What ’s more, you can do it, not through the classic idiom “@@”, but through the wonderful class_attribute. Yes, and redefine them later in the instance. Compare:


#ruby 

@@default_params = {mime_version: "1.0", charset: "UTF-8", content_type: "text/plain", parts_order: [ "text/plain", "text/enriched", "text/html" ]}.freeze

and:

  class_attribute :default_params

  self.default_params = {mime_version: "1.0",

  charset: "UTF-8",

  content_type: "text/plain",

  parts_order: [ "text/plain", "text/enriched", "text/html" ]}.freeze

The difference is significant. Not only do we affect default_params from the instance as we want. But also we can still inherit from the base class and set default_params in the derived class without damaging the base class.

    #irb

    >  class TestClass

    >      @@variable = "var"

    >      def self.variable

    >          # Return the value of this variable

    >            @@variable

    >        end

    >    end

    => :variable

    > TestClass.variable

    => "var"

    > class AnotherClass < TestClass

    >   @@variable = "not var"

    >   def self.variable

    >         # Return the value of this variable

    >           @@variable

    >       end

    >   end

    => :variable

    > AnotherClass.variable

    => "not var"

    > TestClass.variable

    => "not var"

With class_attribute this won’t happen.

Also, instance_values, instance_variable_names methods are not present in the base ruby libraries - these wraps over instance_variables (Object method) are defined in the extension.

The module Date offers huge opportunities. Here you can find the nearest dates by the day of the week, and the date before or after the appointed date at a given interval.

Not to mention friendship Date and DateTime.

  > date = Date.new(2018, 4, 30)

  => Mon, 30 Apr 2018

  > date.beginning_of_day

  => Mon, 30 Apr 2018 00:00:00 WIB +07:00

By the way, in irb the line date = Date.new (2018, 4, 30)will have a completely different output.

  > date = Date.new(2018, 4, 30)

  => #<Date: 2018-04-30 ((2458239j,0s,0n),+0s,2299161j)>

This is also Active Support, and, specifically, the redefinition of the inspect and to_s methods in the Date class.

 # activesupport/lib/active_support/core_ext/date/conversions.rb



  46  def to_formatted_s(format = :default)

  47   if formatter = DATE_FORMATS[format]

  48     if formatter.respond_to?(:call)

  49       formatter.call(self).to_s

  50     else

  51       strftime(formatter)

  52     end

  53   else

  54     to_default_s

  55   end

  56  end

  57 alias_method :to_default_s, :to_s

  58 alias_method :to_s, :to_formatted_xs



...

  61  def readable_inspect

  62    strftime("%a, %d %b %Y")

  63  end

  64  alias_method :default_inspect, :inspect

  65  alias_method :inspect, :readable_inspect

And it’s only the tip of the iceberg

Of course, you should not say that Ruby does not know anything about “safe” sting. You wouldn't find truncate, starts_with?and ends_with?, indentmethods in base libraries. Unable to find all cool stuff like dasherize, titleize, underscore, camelize, demodulize and etc. Would you like to process HTTP request parameters? Use Active Support (methods to_param, to_query). Can’t access to the elements of the objects collection with from,to. There are extensions for Numeric classes (years, days, hours, seconds, kilobytes, megabytes, gigabytes,exabytes) and many other more effective supplements.

Learn more about Active Support advantages here.


Dmitry
Radionov

Backend Developer at JetRockets

Explore more of JetRockets