Active-support: Outside & Inside

“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.

Do you have a project for us?Get a free quote