Jason Charnes

Hanami Models and Making it All Work - Learn Hanami #4

If you’ve made it this far, you should know about routes, controllers, and views in Hanami. In the last Learn Hanami lesson, we’ll be looking at Hanami models.

If you have a Rails (or MVC) background, you’re likely familiar with models. Models represent data your application will use. However, Hanami may utilize models differently than you’re used to.

At this point in the blog application, you’ve been writing; you’ve created an action for displaying posts. This lesson will teach you how to build and display posts using a Post model.

Generating Hanami Models

Hanami has a generator for creating models like the generator you utilized in the last lesson to create an action. To create the model, run the following command from your terminal:

bundle exec hanami generate model post

Being that the generator will create several files, I want you to only look for two:

  • lib/blog/entities/book.rb
  • lib/blog/repositories/book_repository.rb

Folder Structure

I would like to point your attention to where these files live. The model created two files inside the lib directory. The lib folder is where the functionality of Ruby Gems typically live. This structure is intentionally mirroring that.

The idea is to develop our application like a Ruby gem.

With your models inside the lib directory, Hanami models are also available across multiple applications in the apps directory.

Entities

So, entities. What are they?

If you come from a Rails background, you can think of an entity as a model without database interactivity.

An entity is domain object that is defined by its identity.

Similarly, the entity is responsible for business logic. However, unlike Rails Hanami models don’t concern themselves with validation or persistence. This concept can be very confusing, at first. I’m going to skip examples to answer the other question you may have: “How does it work with the database?”

Repositories

Being that entities don’t persist data, might have you wondering how persistence happens.

So, repositories are responsible for “persisting” models.

An object that mediates between entities and the persistence layer.

In fact, a repository has several jobs:

  • take the entity and attempt to persist it to a database
  • retrieve an entity from a database
  • query entities from a database
  • update entities in a database
  • etc.

At this point, it’s best that I show you how to tie it all together with an example.

Migrations

While you may be familiar with migrations, let’s look at Hanami’s definition of a migration:

Migrations are a feature that allows to manage database schema via Ruby.

To put it another way, using a migration will allow you to create database tables, add/remove columns, etc. Luckily, when you ran the generator to create a model, a migration was created for you.

This command creates a migration file inside db/migrations/ with the name ..._create_posts.rb. Open that file, and you should see the following:

Hanami::Model.migration do
change do
create_table :posts do
primary_key :id
 
column :created_at, DateTime, null: false
column :updated_at, DateTime, null: false
end
end
end

This file contains an empty migration that doesn’t do anything right now. You’re about to change that.

In order to show posts, you likely need a posts table in your database. At this point, change your migration file to look like this:

Hanami::Model.migration do
change do
create_table :posts do
primary_key :id
column :title, String, null: false
column :content, 'text', null: false
column :created_at, DateTime, null: false
column :updated_at, DateTime, null: false
end
end
end

This new code uses a DSL to create a database table named posts with five columns.

  • ID (Primary Key)
  • Post Title
  • Post Content
  • Created At
  • Updated At

By default, Hanami uses an SQLite database. This database default allows us not to have to do any setup of our database. I wouldn’t recommend it for production.

At this point, you’re ready to run the migration.

bundle exec hanami db migrate

Once you’ve run the migration, a file named blog_development.sqlite inside the db folder exists.

Instantiating Hanami Models

Hanami can open up a console that loads your Hanami application. To illustrate this in action, open the console by running bundle exec hanami console

Once you have the console running, I’d like you to instantiate a post using Post.new

irb(main):001:0> post = Post.new(title: "My New Post", content: "This is the post.")
=> #<Post:0x007fdb808396a0 @attributes={:title=>"My New Post", :content=>"This is the post."}>
irb(main):002:0> post.title
=> "My New Post"
irb(main):003:0> post.content
=> "This is the post."
irb(main):004:0> post.created_at
=> nil

Without having to provide any information to the Post entity class, it’s aware of title and content. In fact, this functionality comes from automatic schemas. Automatic schemas allow you to instantiate an object with attributes. As a result, you’ll notice in the example you can reference the fields as well.

At this point you might be thinking, “can I call save on this object and it persist to the database?” However, the answer is no. Remember, entities don’t persist data, repositories do.

Saving to a Database

In order to save your entity to the database, I’ll have you pass your newly instantiated model to a repository.

irb(main):001:0> post = Post.new(title: "My New Post", content: "This is the post.")
=> #<Post:0x007f962e1065a0 @attributes={:title=>"My New Post", :content=>"This is the post."}>
irb(main):002:0> repository = PostRepository.new
=> #<PostRepository relations=[:posts]>
irb(main):003:0> post = repository.create(post)
[blog] [INFO] (0.003612s) SELECT `id`, `title`, `content`, `created_at`, `updated_at` FROM `posts` LIMIT 1
[blog] [INFO] (0.001247s) INSERT INTO `posts` (`title`, `content`, `created_at`, `updated_at`) VALUES ('My New Post', 'This is the post.', '2017-06-19 ...', '2017-06-19 ...')
[blog] [INFO] (0.000279s) SELECT `id`, `title`, `content`, `created_at`, `updated_at` FROM `posts` WHERE (`id` IN (2)) ORDER BY `posts`.`id`
=> #<Post:0x007f962e061438 @attributes={:id=>2, :title=>"My New Post", :content=>"This is the post.", :created_at=>2017-06-19 ... UTC, :updated_at=>2017-06-19 ... UTC}>

At this point, you might be wondering what the benefit to having an object with business logic separated from an object responsible for persistence. As a matter of fact, it’s quite convenient to have one object responsible for both. Hanami has an excellent set of reasons why you would want to separate the two:

  • Applications depend on a standard API, instead of low-level details (Dependency Inversion principle)
  • Applications depend on a stable API, that doesn’t change if the storage changes
  • Developers can postpone storage decisions
  • Confines persistence logic to a low level
  • Multiple data sources can easily coexist in an application

Making it All Work

Given everything you’ve learned thus far, you’re now ready to tie it all together.

For the most part, your application should be ready to go. However, add just a small bit of code to complete the feature we’ve been working towards.

Route

To start, change the route in apps/web/config/routes.rb from get '/posts', to: 'posts#index' to get '/', to: 'posts#index'. As a result of this, the homepage of your application will the list of posts.

Passing Data

Next, you’ll need to setup data to get from your controller to your view and subsequently your template. In order for the data to be available, you’ll need to define an Exposure.

Specifically, you’ll need to create a posts exposure in apps/web/controllers/posts/index.rb

module Web::Controllers::Posts
class Index
include Web::Action
expose :posts
 
def call(params)
@posts = PostRepository.new.all
end
end
end

By using an exposure, data isn’t “shared” between the controller and view.

Additionally, you’ll see your newly created exposure being set as an instance variable. The right-hand side of the instance variable is using the repository to grab all the posts from the database.

Using the Data

At this point, we’re just wanting to see the title of all the posts on the home page. Since you won’t be manipulating data for the view, you can go ahead and access this exposure from the template. Now, open up apps/web/templates/posts/index.html.erb and replace it with the following code:

<% posts.each do |post| %%>
<li><%= post.title %%></li>
<% end %%>

In order to verify everything is working, fire up the server and go to http://localhost:2300. At this point, if everything is working correctly you should see:

hanami models

Hooray, this is what you wanted to see: At this point you have a post retrieved from the database.

Additionally, what would happen if you added more posts to the database?

Challenge: Open the console and create more records.

All in all, how did it go? Were you able to create records? If not, refer back to the repository.

If you were able to add new records, you should now see those records appearing on the home page as you refresh it.

Conclusion

Finally, you’ve made it to the end. To summarize, at this point you should be able to create:

  • Routes
  • Actions (Controllers, Views, and Templates)
  • Models (Entities and Repositories)

As previously stated, I believe Hanami is important to the Ruby community. At this point, I hope you’ve been able to understand the basics enough to afterward dig into the Hanami documentation to continue learning.

In conclusion, I’d like to thank you for taking the time to go through this mini-lesson series with me. Because of all that Hanami offers and my limited amount of time, the mini-lesson series was limited to the basics. However, Hanami is a full-featured framework ready for you to use.

Follow me on Twitter.