Jamie Gaskins

Ruby/Rails developer, coffee addict

Clearwater, a Front-End Web Framework in Ruby

Jan 17, 2015 @ 05:29pm

Front-end web development is dominated by JavaScript. This makes perfect sense, seeing as JavaScript is the only programming language that all major browsers implement. But some developers, myself included, are much more comfortable, and therefore more productive, in other languages even when they are well-versed in JavaScript.

Ruby's class-based object model makes a lot more sense to me than JavaScript's prototypical object model. The Uniform Access Principle appeals to me, as well. I had played around with Opal a lot for the past year and, over Thanksgiving weekend with some time to kill, I decided to try my hand at implementing a front-end framework entirely in Ruby. It's called Clearwater.

In a matter of days, I had routing and rendering working using Slim templates (via my opal-slim gem). A few more days of development to fix the back/forward button and clicking links with modifier keys as well as a few rendering issues — the usual problems that crop up in single-page apps. I had almost everything that Ember had except two-way data binding (which I don't think I actually want) in less than a week of actual development.

The bonus is that this framework is written entirely in Ruby using Opal. I TDDed it using RSpec (via the opal-rspec gem). I wrote my demo app entirely in Ruby. I used Ruby idioms throughout, including method_missing for proxy objects. Everything I tried works.

I had a lot of help in writing the framework because I didn't have to write my own template language. I was able to leverage the power of existing Ruby template engines like Slim and Haml using thin wrappers to tell Opal to store them inside its Template namespace. They get converted to JavaScript templates via Sprockets so there is no runtime compilation in production the way there is with server-side templates. This has the side effect of not needing to choose Slim over Haml for performance reasons.

How is application formed?

Your app consists of a primary controller (the main controller holding app state) and a router holding a tree of routes.

router = Clearwater::Router.new do
  route "foo" => FooController.new do
    route "bar" => BarController.new
  end

  route "baz" => BazController.new
end

MyApp = Clearwater::Application.new(
  controller: ApplicationController.new, # This is the default
  router: router # Default router is blank
)

The router adjusts the controller structure according to the URL and then calls the primary controller, which renders the controllers all the way down.

Controllers

Controllers hold application state, so when you've loaded or generated a set of objects, storing them as an instance variable will keep them around for the next time that route gets rendered.

They are also the targets for routing. If your URL path is /articles/1, it may invoke your ArticlesController and an ArticleController.

Controllers are associated with views. There is no naming convention so the view must be specified.

class MyController < Clearwater::Controller
  view { MyView.new }
end

You don't need to have a named view if your view doesn't do anything besides render a template. You can use an anonymous view instance:

class MyController < Clearwater::Controller
  view { Clearwater::View.new(element: '#my-view', template: 'my_view') }
end

Views

Views have two main attributes, configurable either via DSL or inside the initializer.

class MyView < Clearwater::View
  element "#my-view"
  template "my_view"
end

This will rely on there being a container element with the CSS selector #my-view if it needs to be rerendered on its own. If the parent is being rendered as well (such as on initial render or navigation from another route), this isn't necessary.

The template, however, is required. This is the path/filename of the template being rendered. This particular view will render the template in app/assets/javascripts/templates/my_view.slim if you're using the opal-slim gem (or the equivalent Haml template if using opal-haml).

The view serves as the context of the template, so methods defined in the view are available inside the template. This allows for things like form helpers (available by subclassing Clearwater::FormView instead of Clearwater::View).

The view also delegates to the controller, so you can access app state without explicitly requesting it from the controller.

Templates

Templates can be written in your favorite Ruby template language. I prefer Slim so I wrote the opal-slim wrapper to compile Slim templates for Opal. Adam Beynon, the creator of Opal, wrote the opal-haml gem to accomplish the same for Haml (also, thanks to Adam for the inspiration for the opal-slim gem!).

I believe ERb support is built-in, but I haven’t tested that because I don’t use it. :-)

Outlets

Similar to Ember routes, controllers can be nested. Your controllers have an outlet attribute that points to the next subordinate controller. The router wires this up as part of the routing process based on the URL so you should never need to adjust it manually.

Views have an outlet method you can use in your templates. This is used for rendering the subordinate view.

If there is no subordinate route, but you still want to render content where the outlet would go, you can set up a default outlet for the controller:

class MyController < Clearwater::Controller
  default_outlet { MyDefaultOutletController.new }
end

This is useful if, for example, you are rendering a blog but a specific article hasn’t been chosen yet. This default outlet could render the most recent article or the most recent 5-10 or whatever makes sense for your application.

Will it blend?

The most common problems with front-end frameworks are how they break the back button, click events with modifier keys (Command/Ctrl-clicking to open a new tab, for example), and linkability.

Back/forward-button usage is detected by the onpopstate event. The app monitors this event and rerenders based on the updated URL.

Clicking links using any modifier keys are completely ignored by Clearwater, so we can honor the browser's default behavior and not piss off users.

Linkability, the ability to bookmark/share links to specific routes (and, by extension, refresh the page and have it render in the same state), is provided by similar functionality to the back/forward-button handling mentioned above. That is, the app rendering is based entirely on the URL, so once the app loads, it will render the same page it showed before.

"Just use JavaScript, you lazy bum!"

A lot of JS proponents say we should just use JavaScript. That's an easy position to take when it's a language you like, but I don't particularly enjoy JavaScript. When Rails was first announced, developers from the .NET and Java communities said the same things, that we should just use their stack, but Rubyists wanted something better and, as a community, have made Rails one of the most popular web frameworks in the industry.

My point is that we don't have to settle for JavaScript. I’m not trying to say Clearwater is the solution to all your front-end woes. It’s not the next Ruby on Rails (well, it’s possible, but I’m not claiming that right now). However, there are several Opal frameworks on the rise: Volt, Vienna, RubyFire.

"But debugging!"

Another argument people make against using languages that compile to JavaScript is that you still need to understand JavaScript, and that’s absolutely true, but it’s not a convincing argument against Opal.

You also need to understand Java to debug Scala or Clojure apps on the JVM. You need to understand HTTP to build nontrivial Rails apps. You need to understand SQL to do just about anything with ActiveRecord. A lot of abstractions in common usage today have leaks. I don't like that any more than anyone else (to be honest, I complain about it pretty regularly), but it's part of what we do. You always need to understand underlying mechanisms of your app and architecture.

Opal also supplies source maps to the browser to aid debugging purposes in development to show you the Ruby file and line of code (instead of the compiled JS) in stack traces. It's not always perfect, but it's getting better.

So is it usable?

I'm sure there are kinks to work out, but it's usable in its current form. The project readme has an example of a minimal Clearwater application using a simple controller and a Slim template.

Over time, I'll be setting up docs and putting together a few screencasts to show off some more of Clearwater's functionality. In the meantime, if you'd like to chat about it, you can tweet at me or say hi in the Clearwater Gitter channel (I'm @jgaskins in the channel).

Hashie vs OpenStruct vs POROs

Dec 20, 2014 @ 11:45am

At work the other day, we were discussing the merits of Hashie::Mash vs OpenStruct (then yesterday I saw this article from Richard Schneeman which also discusses them). One of the things we discussed was the performance aspect of one over the other. We didn't have any actual benchmarks to use, so I went ahead and wrote some out.

        Hashie alloc    233.387k (± 3.3%) i/s -      1.184M
    OpenStruct alloc     78.249k (± 4.3%) i/s -    395.395k
       Hashie access    549.476k (± 2.1%) i/s -      2.772M
      OStruct access      2.863M (± 4.3%) i/s -     14.390M

This shows that initializing a Hashie::Mash is 2.7x as fast as an OpenStruct, but in accessing attributes (each one had 3 attributes and I hit them all), OpenStruct led by a factor of 5.2.

When you consider that initialization happens once and attribute access happens frequently (you will always access attributes at least as often as initializing, otherwise the object isn't worth a whole heck of a lot), you'd want the one with faster accessors, so you'd want to use OpenStruct, right?

Nope.

The Method Cache and You

All of the major Ruby implementations have a data structure that stores the locations of methods in memory. Looking these methods up every time you access them is slow, so they make use of a cache that speeds up lookup of methods you're actually using. This explanation is intended to be a bit hand-wavy because there are other articles that talk about the method cache. Just know that it's important to the performance of your app.

Several things you can do in Ruby will invalidate some or all of that method cache depending on which Ruby implementation you're using. define_singleton_method, extend, or defining a new class will all do it. When this happens, the VM has to fall back on slow method lookup.

Why do I tell you all this? Because this happens every time you initialize an OpenStruct. This is the reason it's slow to instantiate.

The only way to provide those dynamic accessors without using define_singleton_method is to use method_missing. That is what Hashie::Mash uses. Unfortunately, that comes with its own drawbacks.

Excuse me, have you seen my method?

method_missing is a powerful tool in Ruby, just like dynamic method definition. It helps us do a lot of amazing metaprogramming things. However, that flexibility comes with performance tradeoffs. It's important to realize what all happens before that method is invoked.

  • Send an object a message
  • VM doesn't have a method cached for that message, so we invoke the slow-lookup route I mentioned in the previous section
    • VM checks the object's singleton class (every object has one that contains the methods defined using define_singleton_method; you can check it out using object.singleton_class)
    • VM walks up the ancestor chain of the object's class (object.class.ancestors) to find a method that handles that message.
  • Invoke method_missing

When you send an object a message and the VM doesn't have that method cached (it doesn't exist), so it has to hit the slow-lookup route I mentioned in the previous section. This checks the object's singleton class first (every object has one that contains the methods defined using define_singleton_method; you can check it out using object.singleton_class), then walks up the ancestor chain of the object's class (object.class.ancestors) to find a method that handles that message.

For Hashie::Mash in a Rails app, there are no fewer than 17 classes and modules it has to check. In the benchmark above, there were 9. This means Hashie::Mash will perform significantly worse after Rails injects all those mixins into your ancestor chain. Our app had roughly a 12% decrease in Hashie::Mash access performance.

So … what do you want me to do?

If you want to be able to access attributes with a dot instead of brackets, it really is best to write a class for it. It's faster to allocate and faster to get attributes. Here's the same benchmark as above including plain-old Ruby objects (POROs).

        Hashie alloc    233.387k (± 3.3%) i/s -      1.184M
    OpenStruct alloc     78.249k (± 4.3%) i/s -    395.395k
          PORO alloc    558.183k (± 2.7%) i/s -      2.812M
       Hashie access    549.476k (± 2.1%) i/s -      2.772M
      OStruct access      2.863M (± 4.3%) i/s -     14.390M
         PORO access      8.096M (± 4.9%) i/s -     40.490M

Allocating a PORO is faster than accessing 3 attributes on a Hashie::Mash. In fact, you could allocate a PORO and access all of its attributes with time to spare before a pre-allocated Hashie::Mash just touches its attributes.

Don't get me wrong, using Hashie::Mash or OpenStruct is awesome for prototyping and just getting a feature out the door, but performance is important, so I'd always recommend writing a class for the object you're trying to pass or return. This also has a happy side effect of documenting what that data is; an object telling me explicitly that it's an Article is better than me having to figure it out based on keys and values of a hash.

Mapping vs Every Other Coordinate System

Dec 15, 2014 @ 11:07pm

Last week, I spent a day animating an SVG around a Google Map and it occurred to me that the way we represent polar/Cartesian coordinates is very different from the way map coordinates work.

North is East

The polar axis in a polar coordinate system points to the right. This means 0° is east. On a map, 0° is north.

Run Over Rise

Map coordinates are expressed as (latitude, longitude). Since latitude is the arc from the equator (the horizontal axis) and longitude is the arc from the prime meridian (the vertical axis), this means they are expressed as (y, x). Cartesian coordinates are expressed as (x, y).

Arctangent is a stupid function

This has nothing to do with maps, but everything to do with converting rectangular to polar coordinates. The angle (𝜃 in polar coordinates) is the arctan of the slope, but there is a special case you have to check — if the "run" of the slope is negative, you add π or 180° (depending on whether you're using radians or degrees) to the result.

I understand why this is, but I'd forgotten about it because the last time I did this was in 11th-grade, so I spent half the day trying to figure out why the damn car was going backward around the map half the time.

Apple Pay Is a Big Deal

Sep 11, 2014 @ 03:03am

I've seen an image in various places around the internet (Twitter, Reddit, even in my Facebook comments) where some guy smugly points out that everything new in the iPhone 6 has been in Samsung phones for almost two years.

iPhone 6 vs Samsung Note 4

I'm not going to try to counter everything he points out there (as much as I'd like to), but instead I'm going to focus on the NFC payments part because that's one of the announcements I liked the most.

Basically, my objection boils down to 3 things:

  • Are NFC payments as easy to use on the Samsung as on the iPhone 6?
  • Are they as secure?
  • How many Android users have actually used that feature?

These are actual questions. I don't have the answers because my only experience with Android phones has been troubleshooting ones that my family and friends have. I'm not saying that to talk shit about them; that's actually the only experience I have with them.

Ease of use

The major selling point in the keynote for Apple Pay was how quick and easy it is to pay for something. You hold your phone up to the sensor and put your finger on the Touch ID panel. Done.

If you have to unlock your phone and/or open an app, it's not as easy to use. I'm just saying that Apple Pay is about as easy to use as it gets until you can just walk out the door of the shop without going near a checkout, the shoplifting detectors scanning all the items and charging you all via NFC.

Security

Two major security benefits of Apple Pay are Touch ID and abstraction of credit cards.

Touch ID means that nobody whose fingerprints aren't registered on that specific device can make a payment with Apple Pay. If your phone is stolen, the thief can't buy things with it as they could with a stolen credit card.

Abstracting the card from the payment is another huge deal. If you lose your credit or debit card, you have to cancel the card, which means that everything that gets paid automatically or that stores your card number for convenience will need to be notified of new card details. With Apple Pay, the card details are never transmitted. It generates a single-use payment token and sends that to the merchant, which then talks to Apple (instead of your bank) to get the EFT going.

This way, if the card is stolen, you can just add the new card to Apple Pay and keep paying for things like before. If the phone is stolen, the thief doesn't have access to your payment details.

Now, it's not impossible for someone to get into your phone if they've stolen it. A 4-digit passcode only has 10,000 possible permutations. Assuming 2 seconds per attempt, a thief could try all possible passcodes in an afternoon, but that would require ruthless dedication. It's likely that they wouldn't have to try all of them, though, since they'd stop once they reached yours, but it's also likely they wouldn't be averaging 2 seconds per attempt over a matter of hours.

Once they find your passcode, they can add their fingerprint to Touch ID, which would allow them access to your payments. But you can also quickly and easily disable Apple Pay on a device with the Find My iPhone feature. As long as you do this before they're able to get in — pretty frickin' likely unless you ignore your phone a lot — you're safe.

Actual usage numbers

Even though over half of my friends and family are using Android on Samsung phones, the image at the top of this post is the first time in the nearly two years since Samsung has had NFC payments that I've heard about it. None of them have ever mentioned it. This makes me wonder, have any of them ever used it?

How many Android users in general are using this feature?

When I lived in Australia, NFC payments were pretty common. My debit card was NFC-enabled for payments under $100. I just had to wave it over the payment terminal, collect my receipt and head out. No pin, no signature.

But considering that I've seen very few payment terminals in the US with NFC capability, I doubt a significant percentage of people with this capability on their phones have ever used it or even know it's there. My debit and credit cards are both swipe 'n' scribble — partying like it's 1989.

The reason Apple Pay is such a big deal isn't because the technology is finally being included in Apple phones; it's because Apple has finally convinced major retailers and fast-food restaurants to support it, as well. That's the kicker. The technology alone isn't enough. If Apple merely went the route that Samsung/Google went with their NFC-payments support and let the merchants adopt it at their own pace, it would probably be as useless for the next two years as it has for the previous two.

This has huge implications for Samsung/Google (whoever is responsible for the actual payment support in those phones), as well. In the same way that companies develop mobile apps for both Android and iOS, if they support Apple Pay, they'll likely add support for Android payments, too.

Better alternative to Rails' before_action to load objects

Aug 25, 2014 @ 07:30pm

Rails controllers have a set of callbacks called before_action and after_action (née before_filter and after_filter). They're pretty handy when you want to do something like this:

class AuthenticatedController < ApplicationController
  before_action :require_authentication

  def require_authentication
    # ...
  end
end

class UserProfilesController < AuthenticatedController
  def edit
     # ...
  end
end

In the AuthenticatedController above, we are telling Rails to run the require_authentication method before each action in that controller. Since the UserProfilesController inherits from it, it also inherits that callback.

One thing I see in a lot of code, though, is this:

class ProductsController < ApplicationController
  before_action :find_product, only: [:show, :edit, :update, :destroy]

  def find_product
    @product = Product.find(params[:id])
  end
end

A practice I've been following is never to modify the state of the controller in a callback. Whenever you do, the fact that @product (or whatever instance variable) is set can be surprising the first time you see it because your controller action didn't set it. In large controllers, this can be very difficult to find and if it's set in an inherited controller, it can be downright frustrating.

Another silly thing about using a callback to do this is that you are specifying which actions will be using this instance variable up front. The callback knows which actions are using the objects it loads. If you add a new method that needs that product, you have to add it to the callback list. This is a code smell; the controller itself has direct knowledge of what objects its actions use.

Also, it could be wrong, or at least outdated. For example, if you changed a particular action to load the object a different way than the way it does in the callback (say you want to eager-load some associated objects to avoid N+1 queries), but you forget to remove that action from the callback's list, you're querying the database at least twice for the same record:

class ProductsController < ApplicationController
  before_action :find_product, only: [:show, :edit, :update, :destroy]

  def show
    @product = Product.eager_load(promotions: :discounts).find(params[:id])
  end

  def find_product
    @product = Product.find(params[:id])
  end
end

This isn't necessarily a problem, but it's inefficient because you're querying the database and throwing away the result. It can cause problems, though, if you change how you query the database for that object, say by querying by a slug instead of an id. And then you have to look through the stack trace to try to find out what is firing that query that's broken, but it's a callback, so stack traces are meaningless.

But DRY!

It's important not to repeat the exact same line in every controller action that deals with a singular object:

class ProductsController < ApplicationController
  def show
    @product = Product.find(params[:id])
  end

  def edit
    @product = Product.find(params[:id])
  end

  def update
    @product = Product.find(params[:id])
    # ...
  end

  def destroy
    @product = Product.find(params[:id])
    # ...
  end
end

Here's what I do instead, and I've found it to work very well:

class ProductsController < ApplicationController
  def show
  end

  def edit
  end

  def update
    if product.update_attributes(params[:product])
      # ...
    end
  end

  def destroy
    product.destroy
  end

  private

  def product
    @product ||= Product.find(params[:id])
  end
  helper_method :product

  def new_product
    @new_product ||= Product.new(params[:product])
  end
  helper_method :new_product
end

Notice there is nothing in the show or edit actions, just as if we were using the callback, but we aren't specifying which methods require that object.

To achieve the best of both worlds, we're using memoization, which is just a fancy word for lazy loading. In order to load the Product into memory, we replace our use the @product instance variable with the @product method (as an aside, using accessor methods in your objects is almost always a good idea over using ivars directly).

Wait, there's already a gem for that

You may have seen this pattern before, but not done quite this way. This is almost exactly what the decent_exposure gem does for Rails controllers, but it gives you a handy DSL to do it:

class ProductsController < ApplicationController
  expose(:product) { Product.find(params[:id]) }
end

Turns out, that behavior is wicked-easy to implement:

class ApplicationController < ActionController::Base
  def self.expose(variable_name)
    define_method(variable_name) do
      @exposed_variables ||= {}
      @exposed_variables[variable_name] ||= yield
    end
    helper_method variable_name
  end
end

That is all it takes. Now you can have a nice DSL for these objects in your controller action or your view without adding yet another gem to your project.

It's up to you and/or your team whether you choose the DSL route or to use explicit methods like the first solution I showed. Either way is a much better solution than loading using a callback. In fact, just don't use callbacks. They're almost never what you want.

TwitterGithubRss