Implementing Services in Rails

I’ve been struggling with MVC for a while. There seems to be a problem that I can’t quite put my finger on.

Models describe data.
Controllers route data.
Views show data.

Where do things that don’t fall into those categories go? I’m working on a product right now that needs a search engine. I started working on it, but quickly came back to my doubts about MVC. A search engine is not the model. It has to search through multiple tables. The logic for it is going to be a little big. Putting it in a controller seems like a miss use of the controller as well. Where should it go?

The answer:
Services

Services are amazing. It’s the fourth corner stone of MVC. It should be called MVCS. Everything becomes beautiful and makes sense as you implement services. I’m going to go over what I did from start to finish to on that search feature.

Goal:
Make a search feature that reads searches different or multiple tables based on the first character in the string. @ searches the user table. # searches the posts table. The search field is stored in the nav bar and routes to Users#search. This is the Users#search method inside the UserController:

  def search
    @q = params[:query]
    @results = Search.search(@q)
    render :"/users/index"
  end

That’s a clean looking controller! There’s a trick to it though. “Search.search(@q)” is the service. First create a folder called “services” in the “app” directory. From there we’re going to make a file that shares the name of the class of the service we’re creating. In my case, it was search.rb. Here’s a look at the file structure and how to set-up services in a Rails app:

Screen Shot 2015-04-14 at 3.01.32 PM

 

Once we’re in “services/search.rb,” it becomes pretty easy to implement the search function. To account for upper and lower case characters, we added a column to our tables for a search field.

class Search

  def self.search(query)
    if query[0] == "@"
      clean_search = "%#{query[1..-1]}%".downcase
      Search.search_user(clean_search)
    elsif query[0] == "#"
      clean_search = "%#{query[1..-1]}%".downcase
      Search.search_tag(clean_search)
    else
      clean_search = "%#{query}%".downcase
      Search.search_all(clean_search)
    end
  end

  def self.search_user(q)
    User.where("search_handle LIKE ? or name LIKE ?", q, q) 
  end

  def self.search_tag(q)
    Post.where("search_post_content LIKE ?", q) 
  end

  def self.search_all(q)
    results = []
    results[0] = Search.search_user(q)
    results[1] = Search.search_tag(q)
    results.flatten
  end
end

That feels nice and clean. We have a class that searches. If we need to adjust details about the search, we go to the search service and adjust. It’s not the controller. It’s not the model.

Services are a very important part of the MVC idea. Services are a great way to spread out the logic and reduce complexity.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s