Benito Serna
Trying to build software that works

A decoupled CRUD on Rails

As I said in the last post as ruby/rails developers is very common to start our projects with something very similar to the Rails scaffold…

class PostsController < ApplicationController
  def show
    @post = Post.find(params[:id])
  end

  def index
    @posts = Post.all
  end

  #....
end

… In that post I show you an example of a simple blog app, with a functionality very similar to what a Rails scaffold can do but without coupling our code to Active Record.

The example was using Sinatra, but not to tell you not tu use Rails…

But to tell you that you can write your code in a way, where you can delay decisions like “Which framework should I use?”, or “Which ORM library should I use?”…

You don’t have to make does decisions since the beginning… Or more than that… You don’t have to keep your first day decisions forever!…

If you design your software in a different way…

The same app but with Rails.

You can see the app working here and the code here.

About the design

The behavior of the app is exposed by stateless module functions in the module Blog that lives on lib/blog.rb. And is actually the same code used in the last example.

module Blog
  def self.list_posts(store)
    store.all.sort_by(&:created_at).reverse
  end

  def self.get_post(id, store)
    store.find(id)
  end

  def self.new_post_form
    PostForm.new
  end

  def self.create_post(params, store, current_time = Time.now)
    ProcessPostForm.(params) do |form|
      store.create(form.to_h.merge(created_at: current_time))
    end
  end

  def self.edit_post_form(id, store)
    PostForm.new(store.find(id))
  end

  def self.update_post(post_id, params, store)
    ProcessPostForm.(params) do |form|
      store.update(post_id, form.to_h)
    end
  end

  def self.delete_post(post_id, store)
    store.destroy(post_id)
  end

  #...
end

There is a test file for each CRUD action…

And there is a test file for our “store”…

And now the behavior of our app is exposed on the PostsController of our Rails app

class PostsController < ApplicationController
  def index
    posts = Blog.list_posts(store)
    render :index, locals: { posts: posts }
  end

  def new
    form = Blog.new_post_form
    render locals: { form: form }
  end

  def create
    status = Blog.create_post(params, store)

    if status.success?
      redirect_to posts_path
    else
      render :new, locals: { form: status.form }
    end
  end

  def show
    post = Blog.get_post(params[:id], store)
    render locals: { post: post }
  end

  def edit
    form = Blog.edit_post_form(params[:id], store)
    render locals: { form: form, post_id: params[:id] }
  end

  def update
    status = Blog.update_post(params[:id], params, store)

    if status.success?
      redirect_to posts_path
    else
      render :edit, locals: { form: status.form, post_id: params[:id] }
    end
  end

  def destroy
    Blog.delete_post(params[:id], store)
    redirect_to posts_path
  end

  private

  def store
    BlogStore
  end
end

Don’t you think that is nice to be able to change our framework decision? Don’t you think that is nice to be able to express our business logic decoupled from a specific database technology?

Do you need some help with TDD?

I have an email course with with a guide to help you start with TDD!


Do you want to know more?