Josh Bavari's Thoughts

Thoughts on technology and philosophy

Making Rails and Postgres Arrays Play Nice

less than a 1 minute read

Lately I’ve had a small need to store an array of string values in a Postgres array instead of making foreign tables and dealing with the fun ActiveRecord fancies there are to play with.

Digging into the topic, I was looking for some pre-existing knowledge on the topic. Turns out the people at Relatabase and Stuart Liston at CoderWall had some great knowledge to share on this.

Being the simpleton that I am, I found a ton of knowledge on how to query them, how to add them to your models, but still lacked some basic knowledge of how to USE them. Maybe you’re like me, and this will help.

Adding to Stuart’s code, lets assume we’ve added the postgres column:

1
2
3
4
5
class AddTechToCompanies < ActiveRecord::Migration
  def change
    add_column :companies, :technology_used, :string, array: true, default: '{}'
  end
end

Using Ruby Scopes and the proper Postgres querying syntax:

1
2
3
4
class Company < ActiveRecord::Base
  scope :any_tech, -> (tech){where('? = ANY(technology_used)', tech)}
  scope :all_tech, -> (tech){where('? = ALL(technology_used)', tech)}
end

Simple enough and easy to read. Lets tackle the simple task of adding a company with a few properties: Name, website, and technology_used.

1
2
3
4
5
6
7
#..snip..
company = Company.new
company.name = "RaiseMore"
company.website_url = "http://www.raisemore.com"
company.technology_used = ['Ruby', 'Rails', 'Sinatra', 'JavaScript', 'Redis', 'PhoneGap', 'Sidekiq']

company.save!

This will add our basic info, as well as get us our data back by doing this:

Interactive Console Output
1
2
3
4
5
6
7
8
rails_company = Company.any_tech('Rails')

Company Load (2.6ms)  SELECT "companies".* FROM "companies" WHERE ('Rails' = ANY(technology_used))
 => #<ActiveRecord::Relation [#<Company id: 1, name: "RaiseMore", founded: "2011", website_url: "http://www.raisemore.com", logo_url: "http://res.cloudinary.com/hfjjoialf/image/upload/v1...", description: "We are an event fundraising platform focused on hel...", company_type: "Start up", market: "Charity Fundraising", technology_used: ["Rails", "Postgres", "Sinatra", "PhoneGap", "JavaScript", "HTML5", "Redis"], interns_needed: true, twitter_id: "@raise_more", created_at: "2014-01-13 21:49:39", updated_at: "2014-01-13 21:49:39">]> 

rails_company.technology_used

=> ["Rails", "Postgres", "Sinatra", "PhoneGap", "JavaScript", "HTML5", "Redis"]

Cool, so saving the array data is easy. What about updating? As the relatabase post points out,

One huge caveat of this approach is that rails doesn’t clone the array when saving the original for dirty checking, so any in-place modification to the array won’t register that it’s changed.

This leaves us with these options:

  • set the dirty flag ourselves: rails_company.technology_used_will_change!
  • update using update_attributes: rails_company.update_attributes(:technology_used => ['Rails', 'Redis', 'Go', 'Erlang'] )

Overall results

The way Rails handles arrays is not as complete as a like, but hey, we are skipping a step by getting around foreign key tables. It’s not like you should expect much. I’d say they are allowed for small use projects, but if you really need to do some searching or have referential integrity, stick with foreign key tables.

Also, check out the following resources:

Postgres – The Best Tool You’re Already Using

Rails 4.0 Sneak Peek: PostgreSQL array support

Dont Be a Grunt

less than a 1 minute read

I’ve posted some slides I presented at the OKC Javascript User Group over Grunt.js titled Dont be a grunt, use grunt.

I encourage any and all to take any ideas and information from the slides. I’m hoping they provide value to anyone who may want to learn and use Grunt.

Automating Local IP Lookup With Grunt and Node

less than a 1 minute read

In the last few write-ups I’ve done lately (see the servers post and the phonegap builds post), I’ve been requiring the user to pass in the IP or the host in the command line. That works and all, but I usually have to go look up that ip using the good ol ‘ifconfig’ command.

Since I’m obsesed with automation, I’d rather be lazy and just have the IP Address look up be automatic.

Why am I writing this?

I work in a dozen of different places. At any given time I may be at home, work, a coffee shop, etc. Most times my ip address will be different. I really just want to boot the servers up to my current IP and have the mobile app point at that IP. (The actual device can’t understand localhost or a local 0.0.0.0 IP address over wifi)

Have you guessed it yet? I want to automatically set that ip address / hostname to my local IP without having to go look it up every time.

I found this post on stackoverflow that pointed me at this Node.js documentation to look up the network interfaces. This lets us dig deeper with Grunt to get the IP Address, especially since grunt sits on Node.js.

Simple and (too) easy

There’s a Node.js call that puts all of the config settings into nice JSON for you to work with.

Node.js command for getting network interfaces
1
2
//Gets a JSON much like running 'ifconfig'
var ifaces = os.networkInterfaces();

The next key is to go through all the interfaces, and get the current local IP from the device from ethernet or wifi.

My grunt config looks something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
module.exports = function(grunt) {

  var os=require('os');
  var ifaces=os.networkInterfaces();
  var lookupIpAddress = null;
  for (var dev in ifaces) {
      if(dev != "en1" && dev != "en0") {
          continue;
      }
      ifaces[dev].forEach(function(details){
        if (details.family=='IPv4') {
          lookupIpAddress = details.address;
          break;
        }
      });
  }

  //If an IP Address is passed
  //we're going to use the ip/host from the param
  //passed over the command line 
  //over the ip addressed that was looked up
  var ipAddress = grunt.option('host') || lookupIpAddress;

  grunt.initConfig({
      bgShell: {
          weinre: {
              cmd: 'weinre --httpPort 8080 --boundHost=' + ipAddress,
              bg: false
          },
          rails: {
              cmd: 'cd ../raisemore_web && rails s -p 3000 -b ' + ipAddress
          }
      }
  });

  //Load in the preprocess tasks
  grunt.loadTasks('preprocess');
  require('load-grunt-tasks')(grunt);

  //Tasks to have both servers at local ip and app at local ip
  grunt.registerTask('servers', ['env:dev', 'preprocess:dev', 'bgShell:weinre', 'bgShell:rails']);

  //Task to set up app files pointing at stage ip
  //and setting up weinre at current local ip
  grunt.registerTask('debug', [ 'env:stage', 'preprocess:stage', 'bgShell:weinre']);

}

Now we can just do ‘grunt servers’ to have both servers up at my current local ip or ‘grunt debug’ to get app accessing the stage server and having weinre run locally to debug the app.

Not much to it – call networkInterfaces(), go through JSON, get ipAddress – assign it to the option unless one was passed in. You’re done.

Cheers.

Automating Jasmine Tests With Grunt Karma

less than a 1 minute read

I find myself lately in falling in love unit testing in Javascript with the Jasmine framework. It’s really upped my trust levels that I’ve been writing lately.

I don’t want to go in depth about jasmine and how to use it, but what I do want to do is discuss a little bit of how to automate some of the precious tests you may already have.

Why I wanted to start automating these tests

I had a great set of tests created. Since most of the tests were in javascript, I needed a browser of some sort to act as the test runner.

My initial options for work flow after writing tests:

  • Have a browser (chrome/firefox) pointed at the local SpecRunner.html and manually refresh after making code changes
  • Manually execute phantomJS to get the results from the SpecRunner.html file
  • Have a grunt task that would watch the code files and on change open a browser to execute the code changes

Whats wrong with the prior steps?

It’s manual. You have to take control and do it. That means, another step in the process. I’m obsessed with automation which means I want to automate this.

Do you believe in Karma?

I did a quick google search for a Grunt plugin for automating jasmine tests and stumbled across Grunt Karma . I bet you’re wonder what karma is? Karma is a node.js tool that opens multiple browsers and executes the tests you specify. SA-WEET.

Also to be noted, Karma works with other testing frameworks as well, so you aren’t stuck with just Jasmine.

AUTOMATE ME

Getting started is easy, first running the ‘npm install -g karma’ command to get karma installed, then running ‘karma init’ to set up a config. In the config interactive set up, you specify what testing framework to use, whether or not to use Require.js, what browsers to test with (multiple at a time is an option), what files to test, any files to ignore, and whether or not you want automatically watch and run tests after they change.

Once you’re done, add the grunt config settings for karma as such:

1
2
3
4
5
6
karma: {
  unit: {
    configFile: 'karma.conf.js',
    autoWatch: true
  }
}

If you got it right, fire it off with ‘grunt karma’ – it should automatically start popping up browsers and giving you test statistics.

What I would like to do with these test results

On any code changes or check-ins, I’d like to automatically run the unit tests and update our developer dashboard statistics (we use Dashing ) so we can have live stats of how our code is doing.

The next step in this process is to have Jenkins fire this puppy off when it needs. Perhaps even fire off a build to TestFlight to get the app right into the hands of our tests after all test have verified!

Cheers!

Automating Underlying Mobile Infrastructure With Grunt

less than a 1 minute read

I’m obsessed with automating some of the tasks that I find myself repetitively doing. I don’t know about you, but I hate doing the same thing over and over – it’s boring and pointless

I posted a little while ago regarding automation of phonegap build and deployments, see that here.

Why am I bothering with this?

Our mobile app is the core of what we do. As you may already well know, most mobile apps require an API server serving the application data. In this post, I’d like to address automating the underlying infrastructure that supports the mobile app.

It’s a two-fold win

The main reason is to help get the server stack up without doing all of the repetitive tasks. Getting the mobile infrastructure up can be a slight annoyance, especially when your task is to code.

We have a designer that works on the mobile app styles, and he shouldn’t need to know or care about the requirements of the app just to get to work.

The second reason that I’d want to automate the server stack is due to my frequently using Weinre. I use it a lot to debug either the iOS app or the Android app. Another thing to automate would be to get the weinre server up and have the script for it injected into my source references without having to manually do it.

What I’d prefer we do..

I’d like to just type simple commands… Something I can type to get my local dev servers up, or our designer can type that will get the whole stack and emulator running.

For our designer, I’d want ‘grunt emulate’. For myself, I’d want ‘grunt emulate —weinre=true —host=192.168.1.100’ – either of those commands would do the following:

  • Boot up Rails server at a specified IP
  • Boot up the weinre server at a specified IP (optionally)
  • Preprocess the index.html file to have the weinre javascript source reference
  • Adjust the app settings to be at the specified IP
  • Execute the xcode build command for the iOS project
  • Open the built app on the iOS Simulator

How to get there

To get some of the servers up and running, we need a grunt task that would invoke those processes and would continue running in the background while the other tasks continue running. For this, we’ll need another Grunt plugin, called Grunt bgShell

First we’ll define the background shell tasks in our grunt config file:

Grunt background shell configuration
1
2
3
4
5
6
7
8
9
bgShell: {
  weinre: {
      cmd: 'weinre --httpPort 8080 --boundHost=' + ipAddress,
      bg: true
  },
  rails: {
      cmd: 'cd ../raisemore_web && bundle install && rails s -p 3000 -b ' + ipAddress
  }
}

That covers the servers. Now, for the xcode build and ios-simulator, we’ll use the standard grunt shell plugin to keep running in sync. To invoke the iOS simulator, we’ll use the Node.js package from Phonegap called ios-sim

Before we can use ios-sim, we must invoke the npm installer for it, passing the -g flag for it to be globally installed.

Installing ios-sim
1
sudo npm install ios-sim -g

Now let’s configure the grunt shell tasks for xcode build and iphone simulation:

Grunt shell configuration for xcode build and ios-sim
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
shell: {
  xcodebuild: {
      options: {
        stdout: true
      },
      command: [
          'cd ./ios',
          'xcodebuild -project RaiseMore.xcodeproj -sdk iphonesimulator7.0 -scheme RaiseMore -configuration Debug install DSTROOT=/tmp/RaiseMore' ].join("&&")
  },
  iphonesimulator: {
      options: {
          stdout: true
      },
      command: [
          'ios-sim launch /tmp/RaiseMore/Applications/RaiseMore.app --sdk 5.0'
      ].join("&&")
  }
}

That covers the configuration. Now we just need to have a grunt task that will handle all the preprocessing, start the servers, and then start the build process, and finally run the simulator with the newly built app.

The preprocessing steps were covered in my previous post here and I’ll leave the preprocessing configuration in that post.

All that is left now is to define the grunt tasks, as such:

Grunt task definition
1
2
3
4
5
//Main task for our designer
grunt.registerTask('emulator', ['env:dev', 'preprocess:dev', 'bgShell:weinre', 'bgShell:rails', 'shell:xcodebuild', 'shell:iphonesimulator']);

//Main task I'll probably use, via 'grunt servers --weinre=true --host=192.168.1.100'
grunt.registerTask('servers', ['bgShell:weinre', 'bgShell:rails'])

Using the task

To just boot up the servers, do this:

Invoking the grunt server task
1
grunt servers

If you want the servers and the simulator, do this:

Invoking the grunt emulator task
1
grunt emulator

I know these methods aren’t perfect, as there are a few areas I’d like to improve on. I’d like to have bgShell track the processes to kill them if the task is stopped, or instead to have them ignored if they are already running.

I guess that work will have to be addressed later. Cheers.

Automating Phonegap Builds With Grunt

less than a 1 minute read

One big thorn in my side lately has been getting our Phonegap/Cordova builds out to our team in a timely manner or just getting our mobile app environment set up. Currently our process involves changing a few files up, running some command line prompts, and then hitting a few different IDE’s to crank out builds to manually upload to Testflight (www.testflightapp.com).

What usually happens

“Hey can we please get a new build out to test with?” – DataChomp
“Yea, give me a few minutes to get the build out.” – DevDweeb
“Ok, lemme know when” – DataChomp
—30 minutes later—
“Hows that build coming?” – DataChomp
“Oh man.. not sure.. something messed up along the way. It’ll be a bit more” – DevDweeb
—waits a bit more—-
“Nevermind. I figured it out.” – DataChomp
“Ok whew, cuz it will be a bit more” – DevDweeb

What I wish would happen

“Hey can we please get a new build out to test with?” – DataChomp
“That was done an hour ago, sir” – Jenkins

What is the prob, bob

It’s the process. Here’s what a human would typically go through for the project:

1) Point the build at the correct API end point (localhost/stage/production)
2) Ensure the HTML has the script for weinre remote debugging (may need to be commented out if not needed)
3) Open XCode – build the ios project
4) Take the build from the project – upload to Testflight
5) Open Eclipse – build the android project
6) Take the build from the android project – upload to Testflight

That is a predictable set of steps for a human, but as we all know, humans are prone to make errors. I know I do.

The answer, then, is I didn’t know how bad ass Grunt was and has so many plugins to assist with automation as I do now.

Luckily for me, I found a great post from Jim at imgur, from his post here: http://imgur.com/blog/2013/07/16/tech-tuesday-using-grunt-to-simplify-deployment-processes/

I’m going to dive in to some ways I’ve put together some grunt tasks to accomplish the above tasks.

Introducing Grunt Task Runner

Grunt is a javascript task runner. Learn more at http://gruntjs.com/

The reason I like using it – the config for grunt is in javascript, grunt is lightweight, grunt has very little requirements, and you can get started with a ton of plug-ins available.

I plan on looking at Jenkins to integrate some of these tasks on check-ins for auto or nightly builds. See this post for an idea: http://sideroad.secret.jp/articles/grunt-on-jenkins/

A few plug-ins I’m using so far:

Grunt Shell – https://github.com/sindresorhus/grunt-shell This plugin gives you some shell commands to easily fire off shell commands such as xcodebuild, or even fire up a weinre server.

Grunt preprocess – https://github.com/jsoverson/grunt-preprocess Great tool to combine template files with environment settings to preprocess HTML/Javascript files to drop in IPs or other settings you specify

Grunt env – https://github.com/jsoverson/grunt-env Grunt tasks to automate environment configuration for future tasks.

Removing the human element for app settings

The human must first place the proper host or ip address in place based on where the build may desire to be pointed at as well as whether or not they want Weinre remote debugging (Read about weinre here: http://people.apache.org/~pmuellr/weinre/docs/latest/).

The host/ip address is stored in the appsettings.js file and the weinre remote debugging IP is stored in the index.html page.

First I specified the files that would be preprocessed in the grunt config file. In this case, I specified both appsettings.js and index.html located in a template directory being processed to another location relative from the gruntfile.

Grunt preprocess settings
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
preprocess: {
  dev: {
    files : {
      './appsettings.js': '../tmpl/appsettings.js',
      '../index.html': '../tmpl/index.html'
    }
  },
  prod: {
    files: {
      './appsettings.js': '../tmpl/appsettings.js',
      '../index.html': '../tmpl/index.html'
    }
  },
  stage: {
    files : {
      './appsettings.js': '../tmpl/appsettings.js',
      '../index.html': '../tmpl/index.html'
    }
  }
}

Then I specified the ENV settings in the grunt config:

Grunt env settings
1
2
3
4
5
6
7
8
9
10
11
12
env: {
  dev: {
      NODE_ENV: 'DEVELOPMENT',
      IP_ADDRESS: ipAddress
  },
  prod : {
      NODE_ENV: 'PRODUCTION'
  },
  stage: {
    NODE_ENV: 'STAGE'
  }
}

You’ll notice above, I assigned an ENV variable IP_ADDRESS to a variable ipAddress, which I’ve specified for Grunt as an option that is passed in via command line. That looked something like this snip:

Grunt option for command line parameters
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
module.exports = function(grunt) {
  var ipAddress = grunt.option('host') || 'localhost';
  // Project configuration.
  grunt.initConfig({
    preprocess: {
      dev: {
        files : {
          './appsettings.js': '../tmpl/appsettings.js',
          '../index.html': '../tmpl/index.html'
        }
      }
    },
    env: {
      dev: {
        NODE_ENV: 'Development',
        IP_ADDRESS: ipAddress
      }
    }
  });
};

Using the command line to specify the host, you’d invoke the following grunt command to set up a local dev environment with the server at the specified IP Address:

Passing host/ip address to the grunt tasks
1
grunt preprocess:dev --host=192.168.1.100

Now I need to specify some templates to make use of the ENV variables set up. The grunt preprocess plugin documentation is great, so head there for more info. Here is how I applied it to the two files, appsettings.js and index.html

AppSettings.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
AppSettings = {
  // @if NODE_ENV == 'DEVELOPMENT'
  basePath: "http:///* @echo IP_ADDRESS */:3000/",
  uploadBasePath: "http:///* @echo IP_ADDRESS */:3000/",
  uploadURI: "https://media.address.com",
  debug: true
  // @endif
  // @if NODE_ENV == 'STAGE'
  basePath: "http://stage.server.com/",
  uploadBasePath: "http://stage.server.com/"
  // @endif
  // @if NODE_ENV == 'PRODUCTION'
  basePath: "http://prod.server.com/",
  uploadBasePath: "http://prod.server.com/"
  // @endif  
}

And the template for index.html:

index.html
1
2
3
4
5
.... snip ....
<!-- @if NODE_ENV='DEVELOPMENT' || NODE_ENV='STAGE' -->
<script src="http://<!-- @echo IP_ADDRESS -->:8080/target/target-script-min.js"></script>
<!-- @endif -->
.... snip ....

Removing the human element from app uploads

Another tool that changed the game up for me was the Nomad-cli – a set of tools to build and upload to testflight, amazon, or your FTP choice – found at http://nomad-cli.com/

This gives us a ruby gem we can use to fire off to handle all of our iOS tasks for building and pushing to test flight. The tool I mainly use is called Shenzhen.

A few things are needed. First, we had to create a Gemfile in a subdirectory that used the nomad cli gem:

Gemfile for nomad
1
2
source 'https://rubygems.org'
gem 'nomad-cli'

Using the grunt shell task, I needed to ensure whoever ran this task got the nomad-cli gem first, fired off the command to build and distribute to testflight via shenzhen. It looked like this

Grunt shell command for xcode build
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
shell: {
  testflight: {
    options: {
      stdout: true
    },
    command: [
      'cd ../../',
      'bundle install',
      'cd ./ios',
      'ipa build -p RaiseMore.xcodeproj -c Debug -s RaiseMore',
      'ipa distribute -a <%= testflight_settings.raisemore.apiKey %> -T <%= testflight_settings.raisemore.teamToken -m "' + uploadMessage + '"'
      'cd ../android',
      'ant debug',
      'cd bin',
      "curl http://testflightapp.com/api/builds.json " +
      "-F file=@MainActivity-debug.apk " +
      "-F api_token='<%= testflight_settings.raisemore.apiToken %>' " +
      "-F team_token='<%= testflight_settings.raisemore.teamToken %>' " +
      "-F notes='Some notes for automated upload' " +
      "-F notify=False " +
      "-F distribution_lists='Testers'"
    ].join("&&")
  }
}

As you can see, there are quite a bit of commands contained in that grunt shell task. Let’s step through the steps, just to be clear.

  1. CD to the IOS folder
  2. Call the nomad CLI tool to build iOS app
  3. Call the nomad CLI tool to upload to test flight
  4. CD to the Android folder
  5. Call the ant script to build the app
  6. Curl to upload the file to Test Flight

Perhaps you’re wondering how I got the testflight_settings, I specified those as follows:

Grunt testflight_settings
1
2
3
4
5
6
7
8
grunt.initConfig({
  testflight_settings: {
    raisemore: {
      apiToken: 'some_api_token_here',
      teamToken: 'some_team_token_here'
    }
  }
}

Putting the pieces together

Lets see that Grunt config file now…

Grunt config
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
module.exports = function(grunt) {

  //Options and variables

  var ipAddress = grunt.option('host') || 'localhost';
  var preprocess_files = {
    './appsettings.js': '../tmpl/appsettings.js',
    '../index.html': '../tmpl/index.html'
  };

  // Grunt Plug in configuration.

  grunt.initConfig({
    testflight_settings: {
      raisemore: {
        apiToken: 'apitoken',
        teamToken: 'teamtoken'
      }
    },
    preprocess: {
      dev: {
        files: preprocess_files
      },
      stage: {
        files: preprocess_files
      },
      prod: {
        files: preprocess_files
      }
    },
    env: {
      dev: {
        NODE_ENV: 'Development',
        IP_ADDRESS: ipAddress
      },
      stage: {
        NODE_ENV: 'Staging',
        IP_ADDRESS: ipAddress
      },
      prod: {
        NODE_ENV: 'Production'
      }
    },
    shell: {
      testflight: {
        options: {
          stdout: true
        },
        command: [
          'cd ../../',
          'bundle install',
          'cd ./ios',
          'ipa build -p RaiseMore.xcodeproj -c Debug -s RaiseMore',
          'ipa distribute -a <%= testflight_settings.raisemore.apiKey %> -T <%= testflight_settings.raisemore.teamToken -m "' + uploadMessage + '"'
          'cd ../android',
          'ant debug',
          'cd bin',
          "curl http://testflightapp.com/api/builds.json " +
          "-F file=@MainActivity-debug.apk " +
          "-F api_token='<%= testflight_settings.raisemore.apiToken %>' " +
          "-F team_token='<%= testflight_settings.raisemore.teamToken %>' " +
          "-F notes='Some notes for automated upload' " +
          "-F notify=False " +
          "-F distribution_lists='Testers'"
        ].join("&&")
      }
    }
  });

  //Now grunt tasks
  grunt.registerTask('dev', ['env:dev', 'preprocess:dev']);
  grunt.registerTask('stage', ['env:stage', 'preprocess:stage']);
  grunt.registerTask('prod', ['env:prod', 'preprocess:prod']);

  grunt.registerTask('testflight', ['env:stage', 'preprocess:stage', 'shell:testflight']);
};

So say then, our designer wants to try his design changes out with data from stage. He doesn’t know what files to go touch, and most likely it gets confusing for him. Now with automation, he just types ‘grunt stage’.

Or say, now someone needs to get a build out on test flight for some testers. Simply type ‘grunt testflight’. 1 step is easier and way more predictable than the handful of steps one must jump through.

Although I’m sure there are better ways to do this, I’d love to hear about them. After fighting through tasks such as the grunt testflight plugin, and some vague issues there, the rather clear shell commands provide enough value for myself and my team to automate builds and even have these tasks integrated with any CL like Jenkins.

I hope you can walk away with a few ideas and become more productive. Cheers!

Rev Up Your Rails Engine for Static Assets

less than a 1 minute read

Lately we’ve had the desire to move some of our rails services / middleware from our main web project to Rails Engines as gems to help us scale out and reuse that functionality across servers and sites.

Haven’t heard of Rails Engines? Rails engines are essentially miniture rails applications that provide funtionality to its host application – you could think of engines of what used to be Rails plugins in Rails 2 and upgraded in Rails 3.2. RailsGuides is a great place to start, RailsCasts has a nice walk through. I want to focus on showing some tips, tricks, and pitfuls I’ve encountered to help you be more successful.

Why I started using Rails Engines

For our needs, we had to display a mobile version of our application on our main website webserver. All of the code for the mobile application was hosted in another git repository (Note: I love the Git Gem) and had different themes that needed to be applied. The big question was, how do we have this hosted on different web nodes and control our changes from the mobile repository?

There are so many different ways to approach this problem, the method I chose was to try out a Rails Engine. The main reasons this was attractive was:

  1. A set of controllers to handle the requests for only the mobile application
  2. Not bombarding our web server’s public folder with all of the mobile html/images/javascript/css
  3. Being able to configure the mobile gem based on any environment it may be in
  4. Being able to precompile our assets with the rails asset pipeline
  5. Handle updating the mobile app by just bundle updating the Gem

Rails Engines – Full vs Mountable

There are two types of Rails Engines available, a full engine and a mountable engine (see this StackOverflow article or Adam St Johns Blog for more wisdom). The difference lies in how they handle requests. The full engine sets up routes and controllers as if they were part of the hosting app, whereas the mounted engine is mounted in the hosting app and handles its own routing.

Getting started is easy. For a full engine where all requests are handled as part of hosting app, do this:

rails plugin new NameOfEngine --full

For a mountable engine, where we namespace our requests, do this:

rails plugin new NameOfMountableEngine --mountable

Let’s take a look at how a full engine gets requests routed. For a full engine, we’d have something like this in our Hosting application routing file for everything in our mobile engine.

Rails.application.routes.draw do
    namespace "mobile" do 
        match "/app" => "mobile#app", :via => :get
        match "/modules" => "mobile#modules", :via => :get
        match "/start" => "mobile#start", :via => :get
        match "/tutorials" => "mobile#tutorials", :via => :gets
        match "/" => "mobile#generic", :via => :get
        # Any many more /mobile routes..
    end

    # Other hosting application routing goes here, and mixes in with our mobile routes.
end

Taken straight from Rails Engine Code Comments

… sometimes you want to isolate your engine from the application, especially if your engine has its own router. To do that, you simply need to call +isolate_namespace+. This method requires you to pass a module where all your controllers, helpers and models should be nested to.

Great, so now we dont’ have to uglify our routing file AND we get routing and code reusablity across servers for a mounted engine, that means we have our own routing.rb file in our engine:

MobileEngine::Engine.routes.draw do
    match "/app" => "mobile#app", :via => :get
    match "/" => "mobile#generic", :via => :get
end

We specify the isolate_namespace in our engine file:

module MobileEngine
    class Engine < ::Rails::Engine
        isolate_namespace MobileEngine
    end
end

Finally we mount our engine in the hosting app:

Rails.application.routes.draw do
    mount MobileEngine::Engine => "/mobile"

    #Other hosted application routing
end

The main difference between full and mounted is found in your /lib/name_of_engine/engine.rb file:

Full:

module FullEngine
    class Engine < ::Rails::Engine
    end
end

Mounted:

module MountedEngine
    class Engine < ::Rails::Engine
        isolate_namespace MobileEngine
    end
end

Awesome, so now no matter what, any hosted rails application that uses our mounted engine gets all of its routes namespaced by whatever we choose, in this case, /mobile. Awesome.

Engines and routing / matching controllers and views

Quick reminder on Rails routing:

Rails routes are matched in the order they are specified, so if you have a resources :photos above a get ‘photos/poll’ the show action’s route for the resources line will be matched before the get line. To fix this, move the get line above the resources line so that it is matched first.

One thing to note is the way you namespace your engine which is done with using your engine name in your file structure, both in your controllers and engines, such as this structure:

/mobile_engine
    /app
        /assets
        /controllers
            /mobile_engine
                application_controller.rb
                mobile_controller.rb
        /helpers
            /mobile_engine
        /mailers
            /mobile_engine
        /models
            /mobile_engine
        /views
            /layouts
                application.html.erb
            /mobile_engine
                /mobile
                    app.html.erb

So for our mobile_controller.rb, we simply have this:

# Note the use of Engine.root for finding its path
require File.join(MobileEngine::Engine.root, 'lib', 'mobile_engine', 'file_we_need') 
module MobileEngine
    class MobileController < ApplicationController
        before_filter :adjust_format_for_iphone
        layout false

        def app

            # Code

        end
    end
end

Let’s look at the full request cycle for mounted engines for “/mobile/app”:

  • A request for “/mobile/app” goes through our hosting application, no routes are found for “/mobile/app” for the hosting application routing file, so it looks at the engine.
  • The engine has a route, so it then begins looking in the hosting application for the mobile controller
  • If the hosting application has no mobile controller, it then begins looking at it’s included engines for a mobile controller
  • The engine has a controller, so it uses that controller’s action and goes to render the view.
  • The hosting application begins looking for a view in the hosting application for “views/mobile/app”
  • If the hosting application did not have a view, it then looks for the view in the engine “views/mobile_engine/mobile/app”

Why is this important? I had an issue where I still had a view in my hosting application that was being used over my engine views – so always note that the hosting application trumps its engines controllers and views. Full overriding capabilities is always the control of the hosting application.

Public folder mashing & configuration

The second point in choosing an engine is leaving all of our mobile assets in our mobile engine gem’s public folder so we don’t have to worry about getting those assets copy/pasted between projects that may want the mobile code. Just plunk your JS/CSS/HTML in your public folders as you normally would and make this modification in your engine.rb file in your engine gem:

module MobileEngine
  class Engine < ::Rails::Engine
    isolate_namespace MobileEngine

    # Add any configuration settings to your Engine here

    config.mobile_app_name = "mobile_raisemore"
    config.mobile_app_containing_directory = File.join(root, "vendor").to_s
    config.mobile_app_path = File.join(root, "vendor", "mobile_raisemore").to_s
    config.mobile_theme_path = File.join(root, "public", "mobilethemes").to_s
    config.mobile_repo_url = "git@bitbucket.org:raisemore/mobile_raisemore.git"
    config.local_asset_js_path = File.join(root, "vendor", "assets", "javascripts", "mobile").to_s
    config.local_asset_css_path = File.join(root, "vendor", "assets", "stylesheets", "mobile").to_s
    config.local_public_mobile_path = File.join(root, "public", "mobile").to_s

    # Initializer to combine this engines static assets with the static assets of the hosting site.
    initializer "static assets" do |app|
        app.middleware.insert_before(::ActionDispatch::Static, ::ActionDispatch::Static, "#{root}/public")
    end
  end
end

Then in our hosting application, we can use those configuration settings via an initializer:

MobileEngine::Engine.config.mobile_app_name = "RaiseMore"

Great, we’ve isolated our namespace to leave all requests for mobile_engine, as well as setting up some configuration settings for what the mobile code may depend on, as well as merged our engine’s public folder with the hosting site application.

One thing to note, when using an Engine, it sets up several helpers for you to use. One of which I use there is root, which is the root path of the engine, not the root path of the hosting application. To get the root path of the application, you can use main_app, like this:

<%= link_to "Home", main_app.root_path %>

Precompiling any assets

No difference here, place your assets in your normal locations. Works just like the Rails Asset Pipelines Guide, and documented nicely for gems by Stephen Balls Blog, but essentially still the same.

My file structure for assets looks like this:

/mobile_engine
    /vendor
        /assets
            /images
            /javascripts
                /mobile
                    jquery.js
                    angular.js
                    ...many more...
                mobile.js
            /stylesheets

Mobile.js is nothing special, and looks like this:

//= require ./mobile/

This will package up all javascript files in the /assets/javascripts/mobile folder to be combined and accessed by the hosting application, just run the assets precompile:

rake assets:precompile

Now access them as usual

http://hostingapp.com/assets/mobile.js

The big picture

All in all, I’ve really enjoyed learning about Rails Engines and I can already see several uses for them:

  • Pulling all of our API calls to an engine so we can place that engine on any web nodes and keep API functionality in one place
  • Put together an error handling gem to handle incoming error requests to record and a page to see errors
  • Database models / access methods to be used by other projects

I hope I’ve shed some light on Rails Engines and given a few tips/tricks that I’ve found along the way.