Ruby/Rails Metaprogramming – Creating a custom alerting module

I recently needed to be able to dispatch some very context specific alerts when some of my classes are saved. However, the alert and who gets it are completely different based on the class and the context of quite a bit of business logic. This could be accomplished is several ways, I will explain these and then show how i accomplished it with metaprogramming and modules.

The most straightforward way to have done this would be to just add an alert method in each class and then implement the logic there. There are several reasons to shy away from this including polluting your model, strong coupling and harder testing. However, I shy away from that because it promotes bad coding. Each time you tack on just one more method to a class it makes it that much easier to do it again. I’m not saying never just add a method, but think about whether it could be it’s own thing first.

The next would be to have a giant class that handle the logic. This would be better because all the logic for alerting in the app would be in one spot. However, the logic would have several branches (i.e is this a comment or an image or a new post?).

Ultimately i created an alertable module that I included in my models.

module Alertable
  extend ActiveSupport::Concern

  included do
    after_save :alert
  end

  def alert
    "Alerts::#{self.class}".constantize.new(self).call
  end

end

class Comment < ActiveRecord::Base
  include alertable
end

From the top, ActiveSupport::Concern gives me both class level methods (after_save) and instance methods (def alert). Pretty neat.

The alert method is a bit of metaprogramming that allows me to put the alert logic in it’s own module namespace (aka directory) like this

models/comments.rb #Normal ActiveRecord Model Location
models/alerts/comments.rb #our new Module

Because alertable is include in Comment, when a comment is saved it now does a callback for the method ‘alert’. That is also included from Alertable so after it sees that there are no alert methods defined in the Comment class it calls “Alerts::Comment”.constantize.new(self).call Which is located in models/alerts/comments.rb

The comments.rb file under models/alerts will look something like this.

module Alerts
  class Comment
    def initialize(comment)
      @comment = comment
    end
    
    def call 
      #Do alerting stuff
    end

  end
end

So now any time that i need to do some alerting in my program, i include Alertable and then i can either implement the alert method on the class i’m including it into or (preferably) create a new Alerts::#{class_name} file.

I think this setup is better than the 2 previously mentioned because everything that is related to alerting is not located in a single area of the application. The alerts are their own thing in their own area of the application. The downside is that we have callbacks for the Comment class that are completely encapsulated in a module. So that could cause trouble for newer programmers to the project or if the alert ever returns falsy it would halt the save of our comment and not be immediately apparent as to why it was failing.

Learning AngularJS as a Ruby on Rails programmer

A really awesome project i am working on has a Ruby on Rails backend and uses AngularJS on the frontend. I have been on it for around 1 month now and have spent a ton of my free time learning angular and wanted to document my journey.

My journey to angular through javascript frameworks have been a little rocky and I was bearish about them until this experience. I have tried several others including backbone and ember, which resulted in me dropping back down to just using javascript and jquery.

I am really liking it so far. My biggest positive is that it has a noticeably smoother frontend experience. Biggest downside, when you introduce angular into a project you dramatically increase the surface area of your project’s skillset needs. Not only will you need a backend programmer, designer and product manager, you will now need someone who knows angular, it’s testing frameworks and javascript compilation. You also have to deal with browser incompatibilities.

As far as learning goes, the biggest aha moment was similar to one i experienced a few years ago with rails when i realized it was just well thought out ruby code. So, AngularJS is just well thought out javascript. All it does it handles data, presenting it and updating it when it is changed. I am currently bullish about AngularJS and will update this from time to time with additional resources.

Resources.

First Step: Davetron5000’s Angular Rails Guide
Great book going over basics of how to setup angular with rails.

Second Step: Amazing Intro On Youtube
Watch this at 1.5 or 2x speed. It is phenomenal and was the best at ramping up on fundamentals.

Third Step: Code School
Watch the angular videos several times.

Forth Step (i am here 1 month in): Udemy Course
I am halfway through this and it is filling in the gaps.

Right now i’m capable of writing basic angular and reading intermediate angular. I’ll keep this posted as i advance and find more reasources.

Testing Mulitenancy with Minitest and Capybara

Lately I have used an outside in approach to building my web application in ruby on rails.  What that means is that I am driving my application development by building integration tests.  I picked up this technique a year or so ago from thoughtbot.  So far, i have really enjoyed the insight it has given me and how much less code I write than when i was doing things by building models first.

Many of the applications I end up building as a consultant are multi-tenant application.  So there are many customers that have to live in strict or limited scope from each other.  I have built a really simple helper that I have been using and wanted to share it below in my TestCase class description


class ActiveSupport::TestCaseActiveRecord::Migration.check_pending!
  include Warden::Test::Helpers
  fixtures :all

  def as_user(user)
    login_as users(user.to_sym)
    yield
    logout
  end
end

 

It is super simple and I was a little hesitant to put it out there.  All it does is a login the user, execute the block that is passed in and then log the user out.  In a multitenant app though it is really imporant to check your scopes based on the type of user that is logging in (admin, super admin, org admin, user, anonymous, etc).  On top of that it adds to some very understandable tests.  Ozark and nixa are just two random cities in my area.

 


scenario "Employee cannot visit other organizations tickets" do
  as_user(:ozark_employee) do
    ozark_ticket = organizations(:ozark).tickets.first
    visit ticket_path(ozark_ticket)

    page.text.must_include unauthorized_error
  end
end

scenario "Can edit own tickets" do
  as_user(:ozark_employee) do
    visit tickets_path
    click_link 'Edit'
    fill_in 'Description', :with => 'Some new description xxx'
    click_button 'Update Ticket'

    page.text.must_include 'successfully updated'
    page.text.must_include 'Some new description xxx'
  end
end

This one simple helper has really tied in with my multitenant outside in development and sped up my dev time when building out how different types of users should experience an application.

Script to generate fixtures from a database

So I have used minitest for about 8 months now for testing and all in all it turns out that I loved it more than I expected.  I just moved back to an in progress in house project written using Rspec with FactoryGirl and realized I want my minitest and  fixtures back.  So i found a great script that will generate fixturess based on my database seed.

 

sql = "SELECT * FROM %s"
skip_tables = ["schema_info"]
ActiveRecord::Base.establish_connection
ActiveRecord::Base.connection.tables - skip_tables).each do |table_name|
  i = "000"
  File.open("#{Rails.root}/test/fixtures/#{table_name}.yml", 'w') do |file|
  data = ActiveRecord::Base.connection.select_all(sql % table_name)
    file.write data.inject({}) { |hash, record|
      hash["#{table_name}_#{i.succ!}"] = record
      hash
    }.to_yaml
  end
end

Redirecting To Dynamic Page Using Javascript

So I recently had a consulting opportunity where the client needed to be sent to a dynamic page after a controller action in rails.  That is easy in the normal rails context, however this all had to be done in a javascript content due to it being a javascript based subscription form for sign up.  I ended up with a nice little parlor trick that is attached below.

A little background, this client used recurly to do recurring billing and recurly uses a javascript system to securely handle creditcard data.  On submit, javascript sends recurly the credit card data and they send you back a token representing the data in their system or you get back errors that you can display to the browser.

Once you have the token there are a couple ways you can approach charging it to add a subscription.  The way that I do it is by setting up a subscription/order controller in rails, that then takes care of creating the subscription through an Ajax request. That allows us to catch any errors (i.e. not enough funds) without forcing the user to re-enter any non-credit card data on the site.

Normally after a successful subscription, you will let the form continue to a subscription/registrations controller and that will take care of the setup.

In this app, there were multiple paths that the user could go based on what they order and their progress in the system. So i needed to redirect them dynamically. The way that i did that is on success instead of allowing the form to continue using javascript, i called the function below.

function redirectOrder(url){
    var plan = $('#recurly_plan_code').val();
    var form = $('<form action="' + url + '" method="get">' +
      '<input type="text" name="recurly_plan_code" value="' + plan + '" />' +
      '</form>');
    $('body').append(form);
    form.submit();
  }

Setup Phonegap on Ubuntu 14.04

Worlds succinctest guide to setting up a phone gap project.

  • -> npm install -g phonegap
  • -> npm install -g ripple-emulator
  • MAKE A PROJECT
  • CD to the project
  • -> phonegap build browser
  • ripple emulate

Then open a browser

http://localhost:4400/platforms/browser/www/index.html?enableripple=cordova-3.0.0

Thats it!  Your cordova version may be different, the important part is the URI, which is the path between / and ?

Pundit and Rolify Testing with Rails

Just ran into an oddity that I didn’t expect (due to a false assumptions) when testing permissions and wanted to send it out to the world.

I have an app with two types of roles; :admin or :user. I am using Rolify in combination with Pundit to perform the role based authorization of REST actions.

I mixin a module containing general purpose authorization classes. I do it this way because permissions schemes tend to change alot and centralization makes it easier to change. You could easily just add these to your pundit policy classes.

def is_admin?
  user.has_role?(:admin)
end

def is_user?
  user.has_role?(:user)
end

def is_allowed?
  user.has_role?(:user, record)
end

Pretty straight-forward. An administrator has the role of :admin, a user has a role of :user and a :user is only allowed to access records that they are explicitly granted permission on.

My error was the assumption that user.has_role?(:user) would return true if a user had a role of :user explicitly set on any object in the system. In a pundit action it would look something like this.

def index?
  is_admin? || is_user?
end

The problem is that this was returning false unless someone had a :user role set in general, not on a specific instance of a record.

I think code clears this up further, here is the error.

#Assign the user role of :user on a specific object instance
User.first.add_role(:user, SomeObject.first)
User.first.has_role?(:user) #= false

#Assign the user a role of :user
User.first.add_role(:user)
User.first.has_role?(:user) #= true

So i had to alter this to use see if the User had the role of :user on any object in the system.

#Old definition, broken
def is_user?
  user.has_role?(:user)
end

#New definition, fixed!
def is_user?
  SomeObject.find_roles(:user, user).any?
end

The new is_user? returns true when the user has a role of :user on any SomeObject in the system. Which is exactly what we need.

Lesson learned, I assumed that the method worked a certain way and lost several hours of debugging in my unit tests. I normally will go straight to IRB when i run into anomalies but because i strongly help my assumption I didn’t.

Rails 4.2 truncate_words method

Rails 4.2 added a new feature called truncate words. It allows you to do things like this.

"In a world where everything is awesome".truncate_words(3)
"In a world..."

I wanted to step through this method to look at a few things I found in the code. Here is the full code

def truncate_words(words_count, options = {})
  sep = options[:separator] || /\s+/
  sep = Regexp.escape(sep.to_s) unless Regexp === sep
  if self =~ /\A((?:.+?#{sep}){#{words_count - 1}}.+?)#{sep}.*/m
    $1 + (options[:omission] || '...')
  else
    dup
  end
end

The first thing that I noticed is that it recognizes two additional options
:separator allows you to change the thing that splits up words. For instance, you could do something like cut off the string at the third i. That would look like this.

"Once i went to india and ate ice".truncate_words(3, {separator: 'i'})
"Once i went to india and ate ..."

There is also :omission, which allows you to add something besides … at the end of the truncation.

Now that we know the options, lets look at the code.

The first two lines setup the separator that is used to count the number of words. The first line either takes it from the options hash passed in or defaults it to a space. The second line converts the separator into regular expression language by escaping it (i.e. adding 1 or more \) UNLESS the separator is already of class Regexp. I suspect the unless is so that we don’t get double escaping that isn’t handled already in the Regexp library.

sep = options[:separator] || /\s+/
sep = Regexp.escape(sep.to_s) unless Regexp === sep

Now that we have our separator Regex’ed out, we check the string for it’s match. Self is going to be some sort of string that responds to matching a regex. If it finds a match the truncate_words will return the match (which is stored in the $1 global variable) along with either the specified omission or ‘…’ at the end. If we don’t get enough matches, then the string is just duplicated out.

if self =~ /\A((?:.+?#{sep}){#{words_count - 1}}.+?)#{sep}.*/m
  $1 + (options[:omission] || '...')
else
  dup
end

Something I’m wondering about is why do they duplicate the string at the end if no match is found? Wouldn’t just sending out self work and not eat up a tiny bit more memory?

In my next post I analyze the regex that is used to do matching on this string.