### Make Peace wih JSON and Hash #### Why do I care? # If you've ever needed to build an AJAX route handler, you may have noticed # the prevalence of the design pattern where you return a JSON response. # # post "/comments.json" do # comment = Comment.create(params[:comment]) # comment.to_json # end # # `Ohm` helps you here by providing sensible defaults. It's not very popular, # but `Ohm` actually has a `to_hash` method. # Let's start by requiring `ohm` and `json`. In ruby 1.9, `json` is # actually part of the standard library, so you don't have to install a gem # for it. For ruby 1.8.x, a simple `[sudo] gem install json` will do it. require "ohm" require "json" # Here we define our `Post` model with just a single `attribute` called # `title`. # # We also define a validation, asserting the presence of the `title`. class Post < Ohm::Model attribute :title def validate assert_present :title end end # Now let's load the test framework `cutest` to verify our code. We # also call `Ohm.flush` for each test run. require "cutest" prepare { Ohm.flush } # When we successfully create a `Post`, we can see that it returns # only the *id* and its value in the hash. test "hash representation when created" do post = Post.create(:title => "my post") assert({ :id => "1" } == post.to_hash) end # The JSON representation is actually just `post.to_hash.to_json`, so the # same result, only in JSON, is returned. test "json representation when created" do post = Post.create(:title => "my post") assert("{\"id\":\"1\"}" == post.to_json) end # Let's try and do the opposite now -- that is, purposely try and create # an invalid `Post`. We can see that it returns the `errors` of the # `Post`, because we added an `assert_present :title` in our code above. test "hash representation when validation failed" do post = Post.create assert({ :errors => [[:title, :not_present]]} == post.to_hash) end # As is the case for a valid record, the JSON representation is # still equivalent to `post.to_hash.to_json`. test "json representation when validation failed" do post = Post.create assert("{\"errors\":[[\"title\",\"not_present\"]]}" == post.to_json) end #### Whitelisted approach # Unlike in other frameworks which dumps out all attributes by default, # `Ohm` favors a whitelisted approach where you have to explicitly # declare which attributes you want. # # By default, only `:id` and `:errors` will be available, depending if # it was successfully saved or if there were validation errors. # Let's re-open our Post class, and add a `to_hash` method. class Post def to_hash super.merge(:title => title) end end # Now, let's test that the title is in fact part of `to_hash`. test "customized to_hash" do post = Post.create(:title => "Override FTW?") assert({ :id => "1", :title => "Override FTW?" } == post.to_hash) end #### Conclusion # Ohm has a lot of neat intricacies like this. Some of the things to keep # in mind from this tutorial would be: # # 1. `Ohm` doesn't assume too much about your needs. # 2. If you need a customized version, you can always define it yourself. # 3. Customization is easy using basic OOP principles.