Benito Serna Tips and tools for Ruby on Rails developers

A decoupled CRUD example

February 26, 2019

For us, ruby/rails developers is very common to start our projects/apps with something very similar to the Rails scaffold….

You know, like…

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

  def index
    @posts = Post.all
  end

  #....
end

And in general is hard to find examples of a non-rails way of doing things. Even the basic CRUD…

For example…

Do you know how to build a CRUD without ActiveRecord?

Can you build a CRUD without knowing which database you are going to use? Or which library are you going to use? Can you delay those decisions? Can you make them interchangeable?

If you don’t know how to do it or you have curiosity about how other person would do it… Maybe you can keep reading =)

I want to show you an example of how you can build a simple blog, very similar to what a Rails scaffold can do but without Rails and without a “real database”. I want to show you an example of how you can make those decisions less important…

The app

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

About the design

The behavior of the app is exposed by stateles module functions in the module Blog that lives on lib/blog.rb.

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 the app is exposed using Sinatra… But don’t focus to much on it, that’s not the point… The point is to show you a way of decoupling your business logic from your storage and delivery mechanisms.

store = Blog::Store.new

get "/" do
  posts = Blog.list_posts(store)
  haml :index, locals: { posts: posts }
end

get "/posts/new" do
  form = Blog.new_post_form
  haml :new, locals: { form: form }
end

post "/posts" do
  status = Blog.create_post(params, store)

  if status.success?
    redirect to("/")
  else
    haml :new, locals: { form: status.form }
  end
end

get "/posts/:id" do |id|
  post = Blog.get_post(id, store)
  haml :show, locals: { post: post }
end

get "/posts/:id/edit" do |id|
  form = Blog.edit_post_form(id, store)
  haml :edit, locals: { form: form, post_id: id }
end

put "/posts/:id" do |id|
  status = Blog.update_post(id, params, store)

  if status.success?
    redirect to("/")
  else
    haml :edit, locals: { form: status.form, post_id: id }
  end
end

delete "/posts/:id" do |id|
  Blog.delete_post(id, store)
  redirect to("/")
end

Is this a better to CRUD?

I think that is just a different way… But I think that as a developers is actually better for us to know how to solve a problem in different ways to be able pick the best solution on each situation… We need more examples =)

Related articles

Weekly tips and tools for Ruby on Rails developers

I send an email each week, trying to share knowledge and fixes to common problems and struggles for ruby on rails developers, like How to fetch the latest-N-of-each record or How to test that an specific mail was sent or a Capybara cheatsheet. You can see more examples on Most recent posts or All post by topic.