Blog Archives

Let’s Talk About RubyMotion

Posted in Code, Inside TFG, iPhone, RubyMotion

We’ve just launched Take5 Feedback and its mobile app. This is the fourth mobile app that The Frontier Group has launched, that I’ve worked on.

Previously, we had been using a combination of PhoneGap and SpineJS. This helped us get into the iOS market while still using tools we were familiar with as web developers. After releasing a few apps using PhoneGap and SpineJS, we found that we were consistently spending time during app development fixing things like scrolling, tap events propagating to other views and transition animations. This coupled with the less-than-fantastic debugging tools made us feel that perhaps we needed to re-evaluate our technology choices.

This time we decided to kick it up a notch by moving to RubyMotion. (Bam!).

For those who haven’t heard about RubyMotion, it’s a library that lets you write native iOS apps in Ruby which is then compiled down into Objective-C. As most of us at The Frontier Group are Ruby developers, this felt like a technology well worth exploring. After working with it for a few months, I think it’s the way to go for us moving forward. Now, let’s talk a little about RubyMotion.

Off the bat, one of the big advantages of RubyMotion is that you’re writing native apps in Ruby and not in Objective-C. I’ve found that Objective-C is not an intuitive language and certainly nowhere near as readable as Ruby (especially when method chaining looks like array declaration¬†shudders). One thing RubyMotion has ported over from Objective-C that I do like is the named parameter which makes method declaration/invocation much more readable. For example, let’s say I have a method that generates a receipt and takes a the date of the purchase and the amount in cents as parameters.

In regular Ruby it would be declared something like this:

def generate_receipt date, amount
  # Do something...
end

Now this method could conceivably be called like this:

square = generate_receipt('3/3/2013', 100)

In RubyMotion, however, using named parameters the same declaration/invocation looks like this:

def generateReceiptForDate date, andAmount: amount
  # Do something...
end
square = generateReceiptForDate('3/3/2013', andAmount: amount)

As you can see, it gives the parameters passed in more meaning during the method invocation. Now most Rubyists won’t call methods with “magic numbers/strings” and you can achieve the same thing in < Ruby 2.0 by accepting a hash but I think this is a nice little addition.

Next I’d like to discuss generating and organizing views. MVC in iOS is a little different from MVC in Ruby on Rails, in that views are a little more tightly coupled to the controller. View elements are ultimately defined and positioned in the controller. Those elements will rely on methods which should be defined in the controller as they deal with event handling and navigation. Let’s take a simple example. I want a screen to have a table and when I tap a row I want to navigate to another screen.

class ExampleController < UIViewController

  REUSE_IDENTIFIER = 'example'

  TABLE_FRAME = CGRectMake(0,0,300,400)
  TABLE_ROW_HEIGHT = 30

  def viewDidLoad
    @data = [1,2,3,4]
    table = UITableView.alloc.initWithFrame(TABLE_FRAME)
    table.delegate = self
    table.dataSource = self
    table.rowHeight = TABLE_ROW_HEIGHT
    self.addSubview table
  end

  def tableView(tableView, cellForRowAtIndexPath: indexPath)
    cell = tableView.dequeueReusableCellWithIdentifier(@reuseIdentifier)
    cell ||= UITableCell.alloc.initWithStyle(UITableViewCellStyleDefault,
    reuseIdentifier: REUSE_IDENTIFIER)
    cell.textLabel.text = @data[indexPath.row]
    cell
  end

  def tableView(tableView, didSelectRowAtIndexPath:indexPath)
    tableView.deselectRowAtIndexPath(indexPath, animated: true)
    self.navigationController.pushViewController(AnotherController.new, animated: true)
  end

  def tableView(tableView, numberOfRowsInSection: section)
    @data.count
  end
end

As you can see we’ve got a mix of view level logic (positioning the table on the screen and defining what a row will look like) and controller level logic (setting the data source and the navigational behavior). This makes views and controllers coupled a little tighter than you might be used to. This can lead to extremely fat controllers when you have many elements on the screen each with its own position, look, feel and event handlers. A pattern I found that has worked for me is to have a subclass of a UIView that is responsible for drawing all the elements on a given screen. Let look at an example.

Let’s start by creating our own UIView where we define our table and its look and feel.

class ExampleView < UIView

  TABLE_FRAME = CGRectMake(0,0,300,400)
  TABLE_ROW_HEIGHT = 30

  attr_accessible :table

  def initWithFrame frame
    super
    setupTable
    self
  end

  private

  def setupTable
    @table = UITableView.alloc.initWithFrame(TABLE_FRAME)
    @table.rowHeight = TABLE_ROW_HEIGHT
    self.addSubview @table
  end

end

class ExampleController < UIViewController

  REUSE_IDENTIFIER = 'example'

  def viewDidLoad
    @data = [1,2,3,4]
    self.view = ExampleView.alloc.initWithFrame(view.frame)
    self.view.table.delegate = self
    self.view.table.dataSource = self
  end

  def tableView(tableView, cellForRowAtIndexPath: indexPath)
    cell = tableView.dequeueReusableCellWithIdentifier(@reuseIdentifier)
    cell ||= UITableCell.alloc.initWithStyle(UITableViewCellStyleDefault,
    reuseIdentifier: REUSE_IDENTIFIER)
    cell.textLabel.text = @data[indexPath.row]
    cell
  end

  def tableView(tableView, didSelectRowAtIndexPath:indexPath)
    tableView.deselectRowAtIndexPath(indexPath, animated: true)
    self.navigationController.pushViewController(AnotherController.new, animated: true)
  end

  def tableView(tableView, numberOfRowsInSection: section)
    @data.count
  end

end

Ok so now the responsibility for drawing the table (and any other element) is localized to the ExampleView class and the data manipulation and navigation logic is in the controller which is looking better. We can neaten this up further by making the view responsible for assigning delegates.

Let’s start by creating a new parent view that stores an object which will be the delegate for all elements within the view. In this example the delegate is the ExampleController.

class UIViewWithDelegate < UIView

  attr_accessor :controllerDelegate

  def initWithFrame(frame, andDelegate: delegate)
    self.initWithFrame(frame)
    self.controllerDelegate = delegate
    self
  end

end

Now that we have this class our ExampleView class can inherit from it, allowing the ExampleView to set the delegates for any elements inside it.

class ExampleView < UIViewWithDelegate

  TABLE_FRAME = CGRectMake(0,0,300,400)
  TABLE_ROW_HEIGHT = 30

  attr_accessible :table

  def initWithFrame(frame, andDelegate: delegate)
    super
    setupTable
    self
  end

  private

  def setupTable
    table = UITableView.alloc.initWithFrame(TABLE_FRAME)
    table.delegate = self.controllerDelegate
    table.dataSource = self.controllerDelegate
    self.addSubview table
  end

end

class ExampleController < UIViewController

  REUSE_IDENTIFIER = 'example'

  def viewDidLoad
    @data = [1,2,3,4]
    self.view = ExampleView.alloc.initWithFrame(view.frame)
  end

  def tableView(tableView, cellForRowAtIndexPath: indexPath)
    cell = tableView.dequeueReusableCellWithIdentifier(@reuseIdentifier)
    cell ||= UITableCell.alloc.initWithStyle(UITableViewCellStyleDefault,
    reuseIdentifier: REUSE_IDENTIFIER)
    cell.textLabel.text = @data[indexPath.row]
    cell
  end

  def tableView(tableView, didSelectRowAtIndexPath:indexPath)
    tableView.deselectRowAtIndexPath(indexPath, animated: true)
    self.navigationController.pushViewController(AnotherController.new, animated: true)
  end

  def tableView(tableView, numberOfRowsInSection: section)
    @data.count
  end

end

This has separated the view and controller logic out a little more and our controller is a little thinner. This becomes more useful as the number of elements on your screen increases. The one drawback I found was that my views were ending up with walls of constants defining positions, fonts, etc. Creating subclasses for individual elements that include the position/look/feel, would solve the problem, however you’ll end up with a lot of singleton classes.

Now a little on automated testing. Automated testing is generally a big part of any of the applications we develop. This time however, I found the experience to be so much more costly than testing in a Rails world. When I started developing the app I was aiming for the level of automated coverage we have in our Rails environments. As I attempted to achieve this, I kept hitting road blocks with either the testing framework not behaving as expected, or the mocking libraries placing a significant impact on the code being tested. After some discussion, we decided that due to the simplicity of this app we could achieve an acceptable level of quality and coverage by manual user testing. From this experience though we now know areas that can be improved and are currently developing our skills and tools to ensure that as our apps increase in complexity, we are able to ensure our code quality.

I could write so much more about RubyMotion and the different technical challenges I faced during the development process, but I really just wanted to give my overall impressions. I think it’s a good choice for any Rubyist wanting to venture in iOS development. In combination with the Apple developer docs, which are fantastic, it lets you learn the iOS framework while programming in a language that you’re comfortable with. It’s a well maintained library with the RubyMotion team making constant improvements. My only real criticisms are with the testing and debugging tools not feeling as flexible and as powerful as I’m used to in the Rails environment, but like I’ve said, these tools are being continually developed and improved, and have no doubt these tools will get better with time.

Just Add a Dash of Include to Your Spinach

Posted in Agile Development, Inside TFG, Ruby on Rails, Tips and Tricks

Recently, I switched over from using Cucumber to Spinach in my Rails and mobile projects. I have been enjoying using Spinach so far and thought I’d share a pattern I’ve been using that seems to work for me.

Now I would like to preface this by saying that:

  • I’m not an expert on the topic
  • I’m not saying Spinach is better than Cucumber (or vice versa)

One of the things I found myself doing often when I was naming Cucumber steps was writing names that became very convoluted. I did this because Cucumber scopes all steps globally and naming two steps identically will cause the feature to fail with “Ambiguous match of “step name””. This meant I had to name almost identical steps slightly differently which would get compounded the more similar steps I had.

For example:

And I fill out the object create form with valid details
And I fill out the object create form with valid details using the google auto complete
And I fill out the object create form with valid details using the google auto complete and marking it as draft

When what I really wanted to say was:

And I fill out the form with valid details

All of that other information was covered in the feature name and/or scenario description.

When I moved to Spinach I found that I wasn’t having that problem because each feature’s steps were available only to that feature. I thought this was great… until I found myself defining identical steps in 20 different files.

My friend Tony Issakov then mentioned that you could define modules, include the Spinach DSL and share them between features. I was also warned, by my friend Jordan Maguire, that if you do this too much, you can end up with step definition files that are just includes and aren’t very readable.

For Example:

Feature: Updating an existing object

Background:
  Given I am logged in as an admin
  And there is an object in the system
  And I am on the object index page

Scenario: Successfully updating object
  When I click the object's name
  And I fill out the form with valid details
  Then I should be be on the object's show page
  And I should see the object's details have been update

Scenario: Failed to update object
  When I click the object's name
  And I fill out the form with invalid details
  Then I should see the form again
  And I should see any form errors
class UpdatingAnExistingObject < Spinach::FeatureSteps
  include SharedLinks
  include SharedPath
  include SharedForm
  include SharedObject
  include SharedViews
end

While this is an extreme example you can see that it quickly becomes difficult to know what step is defined where.

After a bit of playing around I found a happy balance between too much and too little step sharing. My general rule of thumb is to only share the following types of steps:

Authentication

module SharedAuthentication

  include Spinach::DSL

  Given 'I am logged in as an admin' do
    visit new_user_session_path
    user = FactoryGirl.create :admin
    login_with user.email, 'secret'
  end

  def login_with email, password
    fill_in 'user[email]', with: email
    fill_in 'user[password]', with: password
    click_button :submit
  end

end

Paths

module SharedPath

  include Spinach::DSL

  And 'I am on the objects index page' do
    visit objects_path
  end

  And "I am on the object's show page for my venue" do
    current_path.should == object_path(@object)
  end

  Then 'I should be on the objects index page' do
    current_path.should == objects_path
  end

end

Forms

Note that I don’t have any of the “And I fill in the form” steps in here. This is because I want separate them out and just have ‘And I fill out the form with valid details’ in my step definition.

module SharedForm

  include Spinach::DSL

  And 'I should see any form errors' do
    page.should have_css 'p.inline-errors'
  end

  def fill_in_object_edit_form attributes = nil
    if attributes.present?
      fill_in 'object[name]', with: attributes[:name]
      # fill in other attributes
    else
      fill_in 'object[name]', with: ''
    end
    click_button :submit
  end
end

Flash

module SharedFlash

  include Spinach::DSL

  And 'I should see a confirmation message' do
    page.should have_css 'p.notice'
  end

  And 'I should see a flash alert' do
    page.should have_css 'p.alert'
  end

end

Object creation

I generally like to have one per model that handles basic object creation.

module SharedObject

  include Spinach::DSL

  And 'there is object in the system' do
    @object = FactoryGirl.create :object
  end

  ## Where mk_two_object is another factory for Object
  And 'there is a mark two object in the system' do
    @mk_two_object = FactoryGirl.create :mk_two_object
  end

end

If we put it all together using the above example we get something that looks like this:
For Example:

Feature: Updating an existing object

Background:
  Given I am logged in as an admin
  And there is an object in the system
  And I am on the object index page

Scenario: Successfully updating object
  When I click the object's name
  And I fill out the form with valid details
  Then I should be be on the object's show page
  And I should see the details have been update

Scenario: Failed to update object
  When I click the object's name
  And I fill out the form with invalid details
  Then I should see the form again
  And I should see any form errors
class UpdatingAnExistingObject < Spinach::FeatureSteps
  include SharedPath
  include SharedForm
  include SharedObject
  include SharedAuthentication

  When "I click the object's name" do
    click_link @object.name
  end

  And 'I fill out the form with valid details' do
    @attributes = FactoryGirl.attributes_for :object
    fill_in_object_edit_form @attributes
  end

  And 'I should see the details have been update' do
    page.should have_content @attributes[:name]
    ## ...
  end

  And 'I fill out the form with invalid details' do
    fill_in_object_edit_form
  end

  Then 'I should see the form again' do
    page.should have_css "form#edit_object_#{@object.id}"
  end
end

Using this pattern I’ve found that I get the step separation that I was looking for in Spinach while keeping the shared functionality that I enjoyed in Cucumber.

I hope other people find this useful.

Search Posts

Featured Posts

Categories

Archives

View more archives