activerecord GROUPed BY in batches

We are all familiar with activerecord .find_in_batches. The main idea behind it is not allowing to load thousands of ActiveRecord objects in memory which would cause tear down the server. I looked at this method and I looked at `.group` which applies GROUP BY clause. Just for fun, I thought what if I receive these “grouped_by” data in batches, something like

 scope.grouped_by_in_batches(group_by_column) do |batch, value|
  batch is an array same as we get in case of `find_in_batches`
  value is the grouped value of the column
 end

Hence I worked on this idea, made a pull request which obliviously did not work, as this was not a common use case, thats all fine. I agree to it. Even I never got to use it till date. However in attempt to do so, I really enjoyed writing test cases for it, getting interacted with a couple of ruby/rails expertise. Feeling happy about it.

Highlight changes being made on ActiveRecord object inside email

I simply needed one helper method which would help me highlight changes being made on any ActiveRecord object inside email. Below is one example of that I have come up with and would like to extend it in my future projects too.

It gave me some insights into ActiveRecord::Base#previous_changes and ActiveRecord::Base#reflections. Nice to learn such ActiveRecord features.

samg/diffy also helped me out decorating text changes

Screenshot from 2014-01-16 13:50:15

Adding to my MailerHelper


module MailerHelper
def highligh_changes_made_to object, options={}
html = ""
attrs = options[:attrs] || object.class.attribute_names
html << "<br/><p>"
attrs.each do |attr|
html << "<p> #{attr.to_s.humanize} : "
if object.previous_changes.include? attr.to_s
if object.class.columns_hash[attr.to_s].type==:text
html << "<br/>"
html << text_changes(object.previous_changes[attr.to_s][0], object.previous_changes[attr.to_s][1])
else
html << "<font color='gray'>#{object_attr object, attr, object.previous_changes[attr.to_s][0]}</font> => "
html << "<b>#{object_attr object, attr, object.previous_changes[attr.to_s][1]}</b>"
end
else
html << "#{object_attr object, attr, (object.send attr.to_sym)}"
end
html << "</p>"
end
html << "<p/><br/>"
html.html_safe
end
def object_attr object, attr, foreign_key_value
return foreign_key_value unless attr.ends_with? "_id"
assoc = object.class.reflections.values.select{ |assoc| assoc.foreign_key==attr }.first
return "<NotFound>" unless assoc
assoc.klass.where(:id => foreign_key_value).first || '""'
end
def text_changes str1, str2
Diffy::Diff.new( str1, str2).collect do |line|
case line
when /^\+/ then
"<b>#{line[1..-1]}</b>"
when /^-/ then
"<font color='gray'>#{line[1..-1]}</font>"
when /^\// then
else
line
end
end.join("<br/>").to_s
end
end
class ActionMailer::Base
helper MailerHelper
end

dealing with concerns

Taking care of concerns, my views are

They are meant not to have large/GOD classes

They are not specific to ActiveRecord

They are there to help us out find out dependencies easier

They should not be dependent on each other, if module A has concern :b, then there should exist paths “root/a.rb” and “/root/a/b.rb“. Though this we ourself have to make sure about concerns path by writing additional code at initialization

I always willing to write clean and neat code, and have end up maintaining my concerns the following ways

  1. Reopening Class
  2. Include Module
  3. Extend ActiveSupport::Concern

I do have one doubt though, out of 3 approaches, which one is good for faster execution?


# config/initializers/concerns.rb
class << ActiveRecord::Base
def concerned_with(*concerns)
concerns.each do |concern|
require_dependency "#{name.underscore}/#{concern}"
klass = concern.to_s.classify.constantize rescue nil
send(:include, klass) if klass.is_a? Module
end
end
end
# Approach #1 opening User class
# app/models/user/validations.rb
class User << ActiveRecord::Base
validates :mobile, :length => { :in => 6..20 }, :allow_blank => true, :allow_nil => true
validates_presence_of :country, :name
before_create :is_zip_validated_by_yahoo?
# and so on
end
# Approach #2 Let User include module
# app/models/user/module_name.rb
module ModuleName
def blah_blah_blah
end # and so on ….
end
# Approach #3 extend ActiveSupport::Concern
# app/models/user/ensure_something_type.rb
module EnsureSomethingType
extend ActiveSupport::Concern
included do
before_create :ensure_something_type
belongs_to :something_type
has_one :something_base_type, :through => :something_type
end
def ensure_something_type
self.something_type ||= SomethingType.find_by_name("something something")
end
end
# app/models/user.rb
  concerned_with  :validations,
    :data_uploader,
    :ensure_something_type,
    :jingo_filters,
    :macro_reports,
    :pricing,

view raw

concerns.rb

hosted with ❤ by GitHub