Permalink
Commits on Aug 12, 2013
  1. rest-of-tutorial

    # Ideas for extending the application
    
    ## Milestones
    
    A pretty obvious addition is to have project milestones, and to be able to associate stories with milestones.
    
    ## Add comments to stories
    
    It's always useful to be able to have a discussion around things, and a trail of comments is a nice easy way to support this.
    
    ## Better users/show page
    
    The current `users/show` page could be improved a lot. For example, it doesn't give any indication of the different projects that stories belong to. What else would be useful on this page?
    
    
    # Appendix -- Styling the Application
    
    **NOTE: This section is a bit out of date. It will mostly work but there might be some style glitches**
    
    The default Hobo theme “Clean” provides comprehensive but minimal styling for all of Hobo’s generic pages and tags. When styling your app you have a choice between creating your own theme from scratch or tweaking an existing theme to suit your needs. The Clean theme has been designed with this in mind; it can be adapted to look very different with only a small amount of effort.
    
    In this section we will adapt our existing theme to create a new look for our app. We will make our changes in `public/stylesheets/application.css`, which is initially empty. This stylesheet is applied after our theme stylesheet so we can override the theme's styles here instead of editing the theme stylesheet directly. This approach means that we can upgrade the theme in the future with minimal effort, although it also means that our stylesheets will be bigger than they could be, so the approach is better suited to small and medium sized projects. For larger projects it might be better to create a new theme, perhaps based on an existing one, or do away with themes altogether and do all the styling in the `application.css` stylesheet.
    
    In order to override our existing theme styles we need to know about the styles that are being applied. For this we can look at the existing theme file in `public/hobothemes/clean/stylesheets/clean.css`. Another good source for this information is by using [Firebug](http://www.firebug.com) in Firefox where we can examine the various page elements to discover what styling is being applied.
    
    Hobo's tags add various CSS classes to the output elements to help with styling. These are typically the name of the tag that was used to generate the output and the name of the model or field corresponding to `this` context. For example:
    
    An index page for "Project" adds the following classes to `<body>`:
    `<body class="index-page project">`
    
    A show page for "Project" adds the following classes to `<body>`:
    `<body class="show-page project">` on `/projects/1`
    
    The `<view>` tag applied to a "Project" name will output:
    `<span class="view project-name">My Project</span>`
    
    The `<card>` tag applied to a "Project" will output:
    `<div class="card project linkable">`
    
    With these classes it becomes very easy to style specific elements on the page. For example:
    
    `.card.project` - Style all "project" cards
    `.index-page .card.project` - Style "project" cards on index pages
    `.show-page.project .card.story` - Style "story" cards on the "project" show page
    
    We'll now add some styling to `public/stylesheets/application.css` to make our Agility app look a bit different.
    
    The first thing we'll do is switch from a "boxed in" look to an horizontally open style. To do this we'll use a background image to draw a horizontal top banner across the whole page and change the page background colour to white:
    
        html, body {
        	background-image: url(/images/header.png);
        	background-position: top left;
        	background-repeat: repeat-x;
        	background-color: white;
        }
    
    Next we'll make the page a bit wider and make the header taller:
    
        body {width: 860px; background: none;}
    
        .page-header {height: 176px; padding: 0; margin-top: 0;}
    
    Next we'll want to position the contents of the page header differently since we've increased its height. We'll start by increasing the size and padding on the application name:
    
        .page-header h1 {
        	margin: 0; padding: 50px 30px 0;
        	font-family: "Lucida Grande", Tahoma, Arial, sans-serif; font-size: 42px; font-weight: bold;
        	text-transform: lowercase;
        }
    
    Next we'll move the main navigation bar to the top right of the page and change the way it looks:
    
        .page-header .main-nav {
        	position: absolute; top: 0; right: 0;
        }
        .page-header .main-nav li {margin-right: 1px;}
        .main-nav a, .main-nav a:hover {
        	padding: 37px 6px 5px; min-width: 95px;
        	text-shadow: none;
        	border: 1px solid black; border-width: 0 0 0 1px; background-color: #D61951;
        }
        .main-nav a:hover {
        	background-color: #AD163D;
        }
    
    Next we need to reposition the account navigation and search bar. We'll also need to reposition our development-mode user changer:
    
        .account-nav {
        	position: absolute; top: 70px; right: 15px;
        	font-size: 11px;
        }
        .account-nav a {color: #bbb;}
    
        .page-header div.search {
        	top: auto; bottom: 0; right: 5px; z-index: 10;
        }
        select.dev-user-changer {top: 100px; left: auto; right: 15px; height: auto;}
    
    Now that we've finished the page header we want to customise the content section of the page:
    
        .page-content {background: none;}
        .content-header, .content-body {margin: 0 25px 15px;}
    
        body {
        	color: #555;
        	font: 14px "Trebuchet MS", Arial, sans-serif; line-height: 150%;
        }
        h1, h2, h3 {font-weight: normal; line-height: 100%; text-transform: lowercase; color: #D61951;}
        h1 {margin: 20px 0 10px; font-size: 26px;}
        h2 {margin: 15px 0 10px; font-size: 18px;}
        h3 {margin: 10px 0 5px;  font-size: 16px;}
        h4 {margin: 10px 0 5px;  font-size: 14px;}
        h5 {margin: 10px 0 5px;  font-size: 12px;}
        h6 {margin: 10px 0 5px;  font-size: 10px;}
    
        .show-page .content-header, .primary-collection h2 {border-bottom: 1px solid #ccc;}
        .front-page .welcome-message {border: none;}
    
        .card {border: none; background: #f2f2f2;}
        a, a:hover, .card a, .card a:hover {background: none; color: #1D7D39;}
    
    Finally we'll customise the look of the aside section which is used on the project show page:
    
        .aside {padding: 20px; margin: 40px 25px 0 0;}
        .aside-content h2, .aside-content h3 {border-bottom: 1px solid #ccc; margin-top: 0;}
    bryanlarsen committed with iox Jun 3, 2013
  2. add-translations

    Translations appear in the `config/locales` directory.  We'll edit
    `app.en.yml` with our translations.
    
    SHOW_PATCH
    bryanlarsen committed with iox Jun 3, 2013
  3. add-translation-keys

    To add translations, you must add translation keys to your code.
    
    For example, to translate one of the strings on the front page:
    
    SHOW_PATCH
    bryanlarsen committed with iox Jun 3, 2013
  4. translating

    # Translating the Application
    
    A lot of Hobos speak languages other than English, so let's translate our application so they feel at home.
    
    First we'll make a couple of temporary tweaks to our configuration.
    
    SHOW_PATCH
    
    The tweak to `config/application.rb` shows you where to change the default locale.  Normally you want to set the locale dynamically, perhaps based on the domain name.   However, that's outside the scope of this tutorial.   See [the Rails guide to i18n](http://guides.rubyonrails.org/i18n.html) for more information.
    
    The tweak to `config/environments/development.rb` tells Hobo to show
    the keys used to generate each string.  This makes the application
    look like a mess, so you'll want to turn this off as soon as you have
    a handle on your translation.
    bryanlarsen committed with iox Jun 3, 2013
  5. create-simple-integration-test

    The next step is to create an integration test using Capybara.  There
    are two parts to this: the magic boilerplate and the meat of the test.
    
    For the magic boiler plate, a quick Google gives us [this
    post](http://blag.ahax.de/post/1581758817/using-capybara-with-plain-rails-integration-tests-and).
    
    For the meat of the test, we refer to the [capybara
    documentation](https://github.com/jnicklas/capybara/blob/master/README.rdoc).
    Look at the section titled `The DSL`, which contains links to
    appropriate sections in the rdoc.
    
    This test is actually part of the Hobo system test suite.
    
    SHOW_PATCH
    
    When your test is written, run it with
    
        $ rake test:integration
    bryanlarsen committed with iox Jun 3, 2013
  6. install-selenium

    # Integration Testing
    
    It's not a real application without tests.  We're going to use
    [Selenium](http://seleniumhq.org/) to do some integration testing.
    
    The easiest way to use selenium with rails is to use `capybara`.   We'll
    also need database_cleaner because capybara/selenium does not support
    transactional fixtures.
    
    Add to your Gemfile:
    
        group :development, :test do
          gem 'capybara', :git => 'git://github.com/jnicklas/capybara.git'
          gem 'database_cleaner;
        end
    bryanlarsen committed with iox Jun 3, 2013
  7. user-permissions-test

    ## User Permissions Test
    
    Let us now replace our trivial test with real tests.   Looking at
    `user.rb` the function most in need of testing is probably
    `update_permitted?`.
    
    The Hobo permission system is not invoked when you simply change
    attributes on a model.  For example, `@user.name = "Another Name"`
    will always succeed even though `User#update_permitted?` sometimes
    doesn't allow the name to change.  To invoke the Hobo permissions
    system, we need to do two things: set the `acting_user` for the model,
    and change the attributes through the Hobo API.  We can do both using
    (`user_update_attributes`)[/manual/permissions#the_permissions_api].
    
    Knowing that, let's create a test:
    
    SHOW_PATCH
    bryanlarsen committed with iox Jun 3, 2013
  8. trivial-unit-test

    Let us write a simple test to verify our factory works:
    
    SHOW_PATCH
    
    now type
    
        $ rake test:units
    
    to verify that your tests run.
    bryanlarsen committed with iox Jun 3, 2013
  9. user-factory

    ## Your First Unit Test
    
    Unit tests are by far the easiest type of test to write in Rails, as
    well as the quickest to run.  However, they can only test code in your
    models.  For this and other reasons, you should try and put as much of
    your program's logic into your model as possible.   (Google for
    "skinny controller, fat model" for other reasons.)
    
    Because Agility is a demonstration program, it really does not have
    much logic in the models.   Looking through our models, we can quickly
    determine that the User model is the heaviest, so let us test that.
    
    We're going to need some data to test, so let us set up a factory:
    
    SHOW_PATCH
    bryanlarsen committed with iox Jun 3, 2013
  10. fixup-test-helper

    The generators did not replace our old fixtures based
    `test_helper.rb`, so lets fix that up:
    
    SHOW_PATCH
    bryanlarsen committed with iox Jun 3, 2013
  11. install-shoulda-and-factory-girl

    # Unit Testing
    
    They say that an application without tests is a broken application, so
    lets add some unit tests.
    
    When starting out with Rails testing, you are faced with a bewildering
    array of choices.   It's outside the scope of this tutorial to guide
    you through these choices, but I will give you one piece of advice:
    any tests are better than no tests.   Do not get caught up in
    "analysis paralysis" -- don't spend so much time trying to analyze
    which choices are right for you that you don't do any testing at all.
    If you change your mind later, it isn't that hard to translate tests
    from one environment to another.
    
    Also be aware that choices that are good for one area are not
    necessarily good for another.  You might use a factory for unit
    testing, but fixtures for integration testing, for example.
    
    For this tutorial we're going to go with a more mainstream choice:
    Test::Unit with Shoulda for the test DSL and FactoryGirl with Faker to
    create test data.
    
    I'm going to give a special shout out here to
    [IRT](https://github.com/ddnexus/irt),  which is written by the same
    man who ported Hobo to Rails3: Domizio Demichelis.  It offers a unique
    paradigm that makes it easy to simultaneously develop code and tests.
    Highly recommended.
    
    We aren't going to use IRT in this tutorial, but there's nothing
    stopping you from writing both Test::Unit and IRT tests in the same
    application.
    
    When we initially generated our application, we didn't customize our
    test framework.   So let's revisit that decision.
    
        $ hobo generate test_framework shoulda --fixture_replacement=factory_girl
        $ bundle install
    bryanlarsen committed with iox Jun 3, 2013
  12. breaking-out-of-the-box

    # Breaking out of the Box
    
    Up until now, we've highlighted cool features of Hobo, and made
    everything look easy.  You can't blame us, can you?
    
    Hobo makes the first 90% of your application really easy, but we
    cannot anticipate everything.  That last 10% is what makes your
    application special, and cannot be included in any toolkit.
    
    Most rapid application generators put you inside a box -- if you stay
    inside the box, everything is easy.  Some won't let you break out of
    the box, and others make it very difficult.
    
    With Hobo there is no box.  More and more customization is required the
    further you stray away from what has been anticipated, but the border
    is not sharp.
    
    In essence, Hobo and DRYML support five different ways of customizing
    a widget, page or action.
    
    ## Parametrization
    
    Most of what you have seen so far in this tutorial has been
    parametrization.  In DRYML you can set the attributes or parameters of
    a tag you invoke, in Ruby you can change the parameters to functions
    you invoke.
    
    ## Extension
    
    In DRYML, there is a tag called
    [extend](/manual/dryml-guide#customising_and_extending_tags).  Extend
    allows you to redefine an existing tag while reusing the existing
    definition.  This allows you to add to the tag or change its default
    parametrization.
    
    You saw an example of extension in DRYML in [Customizing
    Views](#customising_views).
    
    ## Redefinition
    
    The next level of customization is to redefine a tag or function.
    `app/views/taglibs/application.dryml` gets loaded after the RAPID
    library and the auto-generated DRYML, so if you redefine a tag, your
    definition will be used instead of the library definition.
    
    Perhaps the first thing that many developers customize is the
    navigation bar.  In our little tutorial, we want to remove the "Story
    Status" tab.
    
    The nice thing about redefining a tag is that you can use the existing
    definition for a little bit of cut and paste.  Cutting and pasting is
    generally frowned upon -- DRYML includes "don't repeat yourself",
    after all, but sometimes we do it anyways.  In our case, we'll be
    cutting and pasting from the top of `view/taglibs/auto/rapid/pages.dryml`.
    
    SHOW_PATCH
    
    You will notice that we've removed the StoryStatus line.  We've also
    removed the "param" attributes.  Nobody is going to be parameterzing
    our redefinition, so let us make it a little simpler.
    
    ## Defining new tags
    
    Creating new tags is outside of the scope of this tutorial.  Creating
    new tags lets you avoid cutting and pasting, and lets you reuse your
    code throughout your project and into other projects.  For more
    information on DRYML, see the [manual](/manual).
    
    ## Replacement
    
    When you want to do something completely different, you can completely
    bypass the existing tags.   `app/views/projects/show.dryml` can
    contain raw HTML with little or no DRYML.  You can also bypass DRYML
    and create `app/views/projects/show.html.erb` and use standard Ruby on
    Rails techniques.  Hobo models and controllers follow standard Rails
    conventions, so completely custom views can be used without defining
    custom controllers and models or vice versa.
    bryanlarsen committed with iox Jun 3, 2013
  13. granting-write-access-to-others

    It's not enough just to allow others to view your projects, you need to allow some people to make changes, too. The goal of this part of the tutorial is to add a "Contributor" checkbox next to each person in the side-bar.
    
    Implementing this is left as an exercise for the reader. The steps are:
    
    1. Add a boolean `contributor` field to the `ProjectMembership` model.
    2. Modify the permissions of stories and tasks so that users with `contributor=true` on their project membership have update permission for the project.
    3. Use the `<editor>` tag to create an ajax editor for the `contributor` field in the ProjectMembership card.
    
    That's all the hints we're going to give you for this one -- good luck!
    
    Ok, one more hint, here's some associations that might be handy in the Project model:
    
        has_many :contributor_memberships, :class_name => "ProjectMembership", :scope => :contributor
        has_many :contributors, :through => :contributor_memberships, :source => :user
    {: .ruby}
    
    And a helper method that might come in handy when implementing your permission methods:
    
        def accepts_changes_from?(user)
           user.administrator? || user == owner || user.in?(contributors)
        end
    {: .ruby}
    bryanlarsen committed with iox Jun 3, 2013
  14. fix-front-page

    ## Final steps
    
    There's just a couple of things to do to round this part of the tutorial off. Firstly, you might have noticed there's no place to create a new project at the moment. There's also no place that list "Projects you have joined". We'll add both of those to the front page, in the place we currently have a list of "Your projects". Replace that entire `<section class="content-body">` with the following DRYML:
    
    SHOW_PATCH
    
    Notice how we set the context on the entire section to be the current user (`with="&current_user"`). That makes the markup inside the section much more compact and easy to read.
    bryanlarsen committed with iox Jun 3, 2013
  15. removing-members-2

    We have a problem -- the membership card doesn't display the user's name. There are two ways we could fix this. We could either customise the global membership card using `<extend tag="card" for="Membership">` in `application.dryml`, or we could customise *this particular usage* of the membership card. Let's do the latter. Modify the `<collection:memberships>` as follows:
    
    SHOW_PATCH
    bryanlarsen committed with iox Jun 3, 2013
  16. removing-members

    ## Removing members
    
    The sidebar we just implemented has an obvious draw-back -- there's no way to remove members. In typical RESTful style, removing a member is achieved by deleting a membership. What we'd like is a delete button on each card that removes the membership. That means what we really want are *Membership* cards in the sidebar (at the moment they are User cards). So, in `projects/show.dryml`, change:
    
    SHOW_PATCH
    bryanlarsen committed with iox Jun 3, 2013
  17. auto-completion-form

    Now for the form in `projects/show.dryml`. We'll use Hobo's ajax `part` mechanism to refresh the collection without reloading the page:
    
    SHOW_PATCH
    
    Some things to note:
    
     - The `<collection>` tag has `part="members"`. This creates a re-loadable section of the page, much as you would achieve with partials in regular Rails.
    
     - The `<form>` tag has `update="members"`. The presence of this attribute turns the form into an ajax form. Submitting the form will cause the "members" part to be updated.
    
     - The `<name-one>` tag creates an input field for the user association with auto-completion.
    bryanlarsen committed with iox Jun 3, 2013
  18. auto-completion-controller

    ## A form with auto-completion
    
    Now we'll create the form to add a new person to the project. We'll set it up so that you can type the user's name, with auto-completion, in order to add someone to the project.
    
    First we need the controller side of the auto-complete. Add this declaration to `users_controller.rb`:
    
    SHOW_PATCH
    bryanlarsen committed with iox Jun 3, 2013
  19. move-membership-to-sidebar

    ## The view layer
    
    We would like the list of project memberships to appear in a side-bar
    on the project show page, so the page will now display two
    collections: stories and memberships. We can tell Rapid that these are
    the two collections we are interested in using Hobo's view hints.
    Edit the project model like this:
    
    SHOW_PATCH
    
    Note that view hints are defined in the model.   This is not a great
    place for them, but it is better than the alternatives.
    
    It is very common for websites to present information in a hierarchy, and this `children` declaration tells Hobo about the hierarchy of your data. The order is significant; in this example `stories` is the 'main' child relationship, and `memberships` is secondary. The Rapid page generators use this information and place the `stories` collection in the main area of the page, and an aside section will be added for the `memberships`.
    
    Refresh any project page and you should see the collection, which will be empty of course, in a side-bar.
    bryanlarsen committed with iox Jun 3, 2013
  20. update-project-actions

    Finally, now that not all projects are viewable by all users, the projects index page won't work too well. In addition, the top-level New Project page at `/projects/new` isn't suited to our purposes any more. It will fit better with Hobo's RESTful architecture to create projects for specific users, e.g. at `/users/12/projects/new`
    
    So we'll modify the actions provided by the projects controller to:
    
    SHOW_PATCH
    
    Note that there won't be a link to that new-project page by default -- we'll add one in the next section.
    bryanlarsen committed with iox Jun 3, 2013
  21. view-permission-based-on-project-membership

    Note that users now have two collections of projects: `projects` are the projects that users own, and `joined_projects` are the projects they have joined as members.
    
    We can now define view permission on projects, stories and tasks according to project membership.
    
    SHOW_PATCH
    bryanlarsen committed with iox Jun 3, 2013
  22. user-has-many-joined-projects

    And in the User model (remember that User already has an association called `projects` so we need a new name):
    
    SHOW_PATCH
    bryanlarsen committed with iox Jun 3, 2013
  23. project-has-many-members

    Let's do the other ends of those two belongs-to associations. In the Project model:
    
    SHOW_PATCH
    bryanlarsen committed with iox Jun 3, 2013
  24. project-memberships-permissions

    Then permissions -- only the project owner (and admins) can manipulate these project memberships:
    bryanlarsen committed with iox Jun 3, 2013
  25. migrate-project-memberships

    Run the migration generator to have the required foreign keys added to
    the database:
    
        $ hobo g migration
    bryanlarsen committed with iox Jun 3, 2013
  26. project-memberships-add-associations-to-model

    Next, add the associations to the model:
    
    SHOW_PATCH
    bryanlarsen committed with iox Jun 3, 2013
  27. project-memberships-tweak-auto-actions

    First remove the actions we don't need on the `ProjectMembershipsController`:
    
    SHOW_PATCH
    bryanlarsen committed with iox Jun 3, 2013
  28. generate-project-membership

    # Granting read access to others
    
    Now that we've got users owning their own projects, it seems wrong that any signed-up user can view any project. On the other hand it wouldn't make any sense to hide the project from everyone. What we need is a way for the project owner to grant others access.
    
    We can model this with a ProjectMembership model that represents access for a specific user and project:
    
        $ hobo generate resource project_membership
    bryanlarsen committed with iox Jun 3, 2013
  29. project-cards-without-creator-link

    One thing you'll notice is that the project cards have a link to the project owner. In general that's quite useful, but in this context it doesn't make much sense. DRYML is very good at favouring context over consistency -- we can remove that link very easily:
    
    SHOW_PATCH
    bryanlarsen committed with iox Jun 3, 2013
  30. add-your-projects-to-front

    Finally, let's add a handy list of "Your Projects" to the home page. Edit the content-body section of `app/views/front/index.dryml` to be:
    
    SHOW_PATCH
    bryanlarsen committed with iox Jun 3, 2013
  31. project-ownership-migration

    Run the migration generator to see the effect on the app:
    
        $ hobo generate migration
    bryanlarsen committed with iox Jun 3, 2013
  32. task-assignment-permissions

    The stories, tasks and task assignments associated with the project
    need permissions similar to that of their containing project.  Let's
    set their permission to check their containing project:
    
    SHOW_PATCH
    bryanlarsen committed with iox Jun 3, 2013
  33. project-permissions

    How should this affect the permissions? Certain operations on the project should probably be restricted to its owner. We'll use the `owner_is?` helper (that Hobo provides for every `belongs_to` relationship) as it can save an extra database hit. So, edit these permission methods in the Project model:
    
    SHOW_PATCH
    
    Note that in the `create_permitted?` method, we assert that `owner_is? acting_user`. This is very often found in conjunction with `:creator => true`. Together, these mean that the current user can create their own projects only, and the "Owner" form field will be automatically removed from the new project form.
    bryanlarsen committed with iox Jun 3, 2013
  34. users-have-many-projects

    We also need the other end of this association, in the User model:
    
    SHOW_PATCH
    bryanlarsen committed with iox Jun 3, 2013
  35. project-belongs-to-user

    # Project ownership
    
    The next goal for Agility is to move to a full multi-user application, where users can create their own projects and control who has access to them. Rather than make this change in one go, we'll start with a small change that doesn't do much by itself, but is a step in the right direction: making projects be owned by users.
    
    Add the following to the Project model:
    
    SHOW_PATCH
    
    There's a Hobo extension there: `:creator => true` tells Hobo that when creating one of these things, the `owner` association should be automatically set up to be the user doing the create.
    bryanlarsen committed with iox Jun 3, 2013