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.