Misadventures in Code

  • Misadventures in Code
  • A Treatise on Controllers and Nested Resources

    Feb 17

    Decouple yourself from only one controller per resource

    I have an uneasy relationship with conditionals, but I am particularly wary of anything that is more than an if and else, especially in a controller.

    That's not to say I don't believe they have their place, but I'll always look to try and reach for another option, or hoist the conditional up, rather than maintain them in a given method. You see, the issue I have is that complex conditionals, introduce multiple collaborators into a method, reduce the ability to reason about code, and make the system harder to change.

    Steve Klabnik also gives some insight into the issue:

    ActionController relies on instance variables to pass information from the controller to the view. Have you ever seen a 200 line long controller method? I have. Good luck teasing out which instance variables actually get set over the course of all those nested ifs.

    Let's take a look at the below:

    # app/controllers/posts_controller.rb
    class PostsController < ApplicationController
      def index
        ...
        case
        when @user
          @user.posts
        when @company
          @company.posts
        else
          Post.all
        end
      end
      ...
    end
    

    We can reason that the index method has the role of returning a list of posts, and certainly, for the time being, whilst I feel uneasy about the case statement, at least it's returning a collection of the same kind of object. However, it feels off and is certainly harder to test (yes, I do test my controllers), not to mention, flies in the face of tell don't ask.

    What happens when we start to introduce logic into our views as a result of this case statement though? Where we check for the presence of an instance variable, to determine what and what not to display? If I start reaching for a presenter, this still leaves the underlying problem in our action.

    What happens when we introduce authorization and roles into the mix? All of a sudden our method is now involved in determining what to display by virtue of a users role, as well as their scope.

    Whilst the tight coupling between our controllers and our views certainly doesn't help, I'm of the strong opinion that by increasing the controllers, we can reduce logic in our views as well as increase code clarity. This is not a new or original concept, Rails anti-patterns, describes the same issue

    At some point - a point that comes more quickly than you might think - it becomes beneficial not to try to keep the same controller for each difference resource path but to instead create a new controller for each nesting.

    Whilst our contrived example may not be at that point just yet, it won't be long before it is.

    So let's split these controllers up

    # app/controllers/posts_controller.rb
    class PostsController < ApplicationController
      def index
        Post.all
      end
    end
    
    # app/controllers/users/posts_controller.rb
    class PostsController < ApplicationController
      def index
        @user.posts
      end
    end
    
    # app/controllers/companies/posts_controller.rb
    class PostsController < ApplicationController
      def index
        @company.posts
      end
    end
    

    At the cost of more classes, our extremely simple example is now easier to reason about and to change. Shared behaviour can be included via concerns, and views either replaced completely, composed of partials, or we can wrap our collections with presenter objects to properly house the logic that remains in the view layer. We are now further enabled to make changes to our code, without being too concerned about our other actors and we have removed condtionals from our controller actions and pushed the decision making process up to our router.

    A Note on CanCan

    load_and_authorize hides the logic that can determine which collection of a given resource to display away. So in our contrived example, we wouldn't necessarily have a case statement, unless we needed a scope on one of our actions. Favouring explicitness over implicitness aside, it obfuscates the conditional, but still has all the downsides. Any changes we make to our controller action, now has to deal with the additional actors, as well as is now harder to reason about when or if a particular parent resource is loaded. This is why im considering a move to pundit, but that's another post.

  • getting started with ember, part 2

    Jul 1

    Hamburger, I mean Handlebar Helpers

    Before we get started I'd like to give a huge thank you to @geoffreyd, he pretty much fixed all my code and set me straight on some really good best practices, not to mention spent an enormous amount of time putting up with my help vampirism. Additionally all the code for this post can be found on github

    So far we've displayed our transactions on the page, but the amount property in our transaction model is just using a number to represent its value and we want to format it. So we're going to utilise a helper method so it's available across our views.

    Helpers in ember are not dissimilar to helpers in Rails, particularly in this use case where we're planning on formatting an existing value. The main difference is by utilising ember, we can ensure our helper method updates as the values it depends upon changes.

    Today we're going to register a new helper and convert our transaction amount to something resembling currency.

    Ember CLI gives us a few options to achieve our goals, but in this case what we need is the following:

    import Ember from "ember";
    export default Ember.Handlebars.makeBoundHelper(function(amount, options){
      var amount = (amount/100).toFixed(2);
      var currencyAmount = "$"+ amount + "";
      return currencyAmount;
    }); 
    

    Insert the above into app/helpers/to-currency.js.

    We use makeBoundHelper here instead of just helper or registerBoundHelper due to ember-cli. Ember-cli utilises the makeBoundHelper and makes it accessible to the ember container, this is outside the scope of this post, but if you'd like to dig a little into our applications internals within the console, run Transactions.__container__ within your browser.

    The alternative to using this is using registerBoundHelper is that we'd need to take the extra step to define the helper function, then register our helper within app.js.

    If we reload our application now we should see our transaction amount looking all pretty.

    Routes

    This is where most guides would start discussing Ember controllers and creating new transactions from within. We're not going down that path.

    A Route in Ember represents state, which is represented with a url. As we're going to be manipulating data, and thus the state of the current page, we're going to store our actions relating to creating a new transaction in a route. To start though we need to specify a new route for the user to hit.

    Router.map(function() {
      this.resource('transactions', { path: '/' }, function(){
        this.route('new'); 
      });
    });
    

    Here we have made /transactions/new available. This, by convention, will ensure Ember generates a TransactionsNewRoute, but we're going to create one of our own and start handling user input. To take user input we need a form, so let's create a new template for our transactions form:

    <form {{action "createTransaction" on="submit"}}>
      <label for="transaction-name">Name:</label>
      {{input type="text" value=name id="name"}}
    
      <label for="transaction-amount">Transaction Amount:</label>
      {{input value=dollarValue id="comp-currency"}}
    
      <button type="submit">Save Transaction</button>
    </form>
    

    In Ember, if we don't assign a string to a value, that then becomes available to the context object within the template, bear this in mind when we start to look at our Route object later on.

    So now that we have a form, let's make our form save our transactions with a Route:

    import Ember from 'ember';
    
    export default Ember.Route.extend({
        model: function(){
            return this.store.createRecord("transaction", { amount: 0, name: 'New Transaction' });
        },
    
        actions: {
          createTransaction: function(){
            var self = this;
            var transaction = this.controller.get('model');
            // TODO: set date format the way you want it.
            transaction.set('occurredOn', new Date);
            transaction.save().then(function(){
              self.transitionTo('transactions.index');
            });
          }
        }
    
    });
    

    Ok, there's a lot going on here, so lets break it down.

    First up, we instantiate a brand new instance of transaction within the route, place it inside the model hook, and set it with some defaults using the createRecord method. This then sends the model to the controller, which if you remember from our first post talks exclusively to the template.

    Next is our actions, specifically our createTransaction action. We can define actions within a Route object, a Controller or a View. To make understanding when to use which earlier, if you're dealing with data changes, use the Route, if you're dealing with changes within the object or collection, use a Controller, if you're dealing with interactions between views, use the View.

    Within our createTransaction action, we retrieve our controller model, which seems a bit odd as we've defined it above. However we need to explicitly retrieve the model object as it stands from the controller, which as we reminded ourselves above, talks to the template. So here, we get the object as it stands in our form. We then save our new transaction, and make use of promises to return to the transactions index once complete.

    There's one piece to puzzle missing, how does our dollarValue from our template, map to the amount on our model. The answer is in fact, in our model.

    Computed Properties

    One of Ember's most powerful features is computed properties.

    It allows you to declare functions as properties on an object. When we create a computed property we defined the properties of the object it depends on at the end of the function, we can also use this to build a computed property that depends on another computed property. Awesome!

    In our case below we have a computed property of dollarValue to map dollar values to cents in our database. When we use this property in our form, it sets the amount to the unformatted amount entered in. So when it comes time to saving the form, it automatically does all the adjustments for us.

    var Transaction = DS.Model.extend({
        name: DS.attr('string'),
        amount: DS.attr('number'),
        occurredOn: DS.attr('date'),
        dollarValue: function(key, value, previousValue) {
            var currency;
            if (value !== undefined) {  // set was called
                var amount = Math.round(value * 100);
                currency = accounting.unformat(amount);
                this.set("amount", currency);
            } else {
              currency = this.get('amount');
            }
    
            // Get the value and return for either set or get.
            amount = (currency/100).toFixed(2);
            return accounting.formatMoney(amount, "");
        }.property('amount')
    });
    

    One more thing

    There's just one thing we need to do, and that's instantiate an Ember Object Controller so our form gets all the attributes proxied through. So let's create a new TransactionsNew controller:

    import Ember from "ember";
    
    // Make TransactionsNewController an ObjectController,ยท
    // so that it will proxy properties. !important
    export default Ember.ObjectController.extend({
    
    });
    

  • Getting Started With Ember.js

    Jun 29

    Ember CLI

    Ember is an opinionated JavaScript framework, it subscribes to the Rails philosophy of convention over configuration and makes it very easy to start building well structured, rich, client side applications.

    Ember cli is a tool used to help build Ember applications, and similarly has a strong opinion on how you should structure and work with your code. Today's blog post is going to be using both of these tools.

    As a Rails developer, the urge to utilise the ember-rails gem is pretty high, however I'm of the opinion that you should keep your Ember application separate from your Rails app and Ember cli provides too good a tool to ignore.

    Ember cli is a command line tool thats based on the Ember App Kit project, it is meant to function as a replacement to Ember App Kit. It gives us a great project structure out of the box (not unlike when you generate a new Rails project with rails new), and a few handy generators.

    To get started, please follow the instructions at ember cli, and come back when you're ready to proceed.

    We're going to be making a simple budgeting application, we'll take incoming transactions, assign them a name, a date, and a price, and assign them to a category.

    To start simply run ember new transactions this will create a new transactions folder and generate the application structure. Already we can run ember server from within our application and navigate to our ember app.

    To help us identify what's going on in our application, we're going to navigate to our app.js file and add the following:

    var App = Ember.Application.extend({
      ...
      LOG_TRANSITIONS: true,
      LOG_TRANSITIONS_INTERNAL: true
    });
    

    This will assist us in getting a better understanding of what's going on under the hood.

    The Router

    Next we're going to start setting up some of our routes within app/router.js. The Router in Ember, as opposed to individual route objects, is much like a router in Rails. The main point of difference between a router in Rails and a router in Ember is that a route in Ember represents any one of the possible states of your application, which in turn provides a url.

    To start we're going to add our first real route, which will display an index of transactions:

    Router.map(function() {
      this.resource('transactions', { path: '/' });
    });
    

    Note the addition of the path option, this tells Ember to render the transactions template when visiting the root path of your application.

    When you give it a route like the above, it tells Ember that it needs an IndexRoute, a TransactionsRoute and a TransactionsIndexRoute as well as controllers and templates following the same naming convention eg TransactionsIndexController and transactions/index.

    What Ember will do for you is generate those required routes, controllers and templates for you, so you can continue to develop your application until you need to override the default behaviour within those files.

    The Model

    All this routing needs a model to back it. Once again there's some similarities with Rails here so go ahead and create a model in app/models/transaction.js:

    import DS from 'ember-data';
    
    var Transaction = DS.Model.extend({
      name: DS.attr('string'),
      amount: DS.attr('number'),
      occurredOn: DS.attr('date')
    });
    
    Transaction.reopenClass({
      FIXTURES: [
      {
        id: 1,
          name: "GYG Burritos",
          amount: "1200",
          occurredOn: "12/04/2014" 
      }
    ]});
    
    export default Transaction;
    

    We've imported Ember Data, using the import statement at the top of the page. Ember cli provides us the mechanism for this as it uses ES6 modules. How this happens is a bit beyond the scope of this post, however ES6 modules are not fully supported yet, so Ember uses some crafty magic to make these available to use across the board.

    In addition we have also defined our model, given it some attributes with different types and created an initial fixture for us to work with. To utilise fixtures, open up app/adapters/application.js and add the following:

    import DS from 'ember-data';
    export default DS.FixtureAdapter.extend();
    

    This says to Ember to utilise fixtures as our data source.

    A Route

    Now we want to make sure when we visit the index of our application we see our list of transactions. To accomplish this, we need to use a route. A route helps define what model should be available to your template when we view our transactions, this is achieved with the model hook in the route.

    For our first route, we're going to create the TransactionsRoute in app/routes/transactions.js

    import Ember from 'ember';
    var TransactionsRoute = Ember.Route.extend({
      model: function(){
        return this.store.find('transaction');
      }
    });
    
    export default TransactionsRoute;
    

    Note the import of Ember, this is another Ember cli requirement that whenever we need ember-data or Ember in our files, we import either of those depending on our needs.

    We also return our available transactions in our model hook

    A template

    We've setup our routes, our model and some initial data, now we need to display our transactions. So we will setup a transactions template in app/templates/transactions.hbs

    <ul>
      {{#each}}
        <li>{{name}} {{amount}} {{occurredOn}}</li>
      {{/each}}
    </ul>
    

    The each here loops through an array of records, and although we only have one transaction to return, this will ensure our data is displayed correctly and will support additional records when we progress to adding them.

    Now if we run ember server from our terminal we should see our fixture data displayed for us.

    I should take a step back here and explain that even though ember knows to go and find the transactions template, it's the {{outlet}} statement in app/templates/application.hbs which enables the template to bubble up to be displayed.

    We'll be utilizing this concept further when we add additional features, but for now our list of transactions can remain in our transactions template.

    We'll continue on from here in the next blog post, but for now if you'd like to take a look at the source code, you can find it on github

    A Note on Controllers

    We didn't look too closely at Ember Controllers in this post, this will come up in the next post in the series, however ember has created all the controllers we have needed and so far we haven't had to extend their base functionality from what we already have.

  • Recent Articles
    1. A Treatise on Controllers and Nested Resources Feb 17
    2. getting started with ember, part 2 Jul 1
    3. Getting Started With Ember.js Jun 29
  • Tags
    1. js (2)
    2. ember.js (1)
    3. ember (1)
    4. rails (1)
    5. controllers (1)