Commit 84590916

mo <mokha@cisco.com>
2017-05-06 15:32:42
fix search scopes for cakes controller.
1 parent ff4fdd2
Changed files (4)
app/models/concerns/queryable.rb
@@ -1,11 +1,70 @@
 module Queryable
   extend ActiveSupport::Concern
 
+  included { send :include, ClassMethods }
+
   module ClassMethods
-    def all_matching(search_filters)
-      search_filters.inject(self) do |memo, next_filter|
-        next_filter.call(memo)
+    def filter_by(filters)
+      return all if filters.empty?
+      filters.reduce(self) do |current_scope, filter|
+        filter.call(current_scope)
+      end
+    end
+
+    def query_builder_for(params)
+      Queryable::Builder.new(params).tap do |builder|
+        yield builder if block_given?
+      end
+    end
+  end
+
+  class Builder
+    include Enumerable
+    attr_reader :params, :filters
+    delegate :empty?, to: :filters
+
+    def initialize(params)
+      @params = params
+      @filters = []
+    end
+
+    def always(&filter)
+      add do |relation|
+        filter.call(relation)
+      end
+    end
+
+    def if_present(key, &filter)
+      return if params[key].nil? || params[key] == ''
+      add do |relation|
+        filter.call(relation, normalize(params[key]))
       end
     end
+
+    def each(&block)
+      @filters.each(&block)
+    end
+
+    private
+
+    def add(&filter)
+      @filters.push(filter)
+    end
+
+    def boolean?(value)
+      true?(value) || false?(value)
+    end
+
+    def true?(value)
+      "true" == value.to_s.downcase
+    end
+
+    def false?(value)
+      "false" == value.to_s.downcase
+    end
+
+    def normalize(value)
+      boolean?(value) ? true?(value) : value
+    end
   end
 end
app/models/creation/repository.rb
@@ -3,6 +3,9 @@ class Creation
   scope :tagged, ->(tag) { tagged_with([tag]).where('photos_count > 0') }
   scope :published, ->{ joins(:photos).where(photos: { image_processing: nil }) }
   scope :search, ->(query) { where(["UPPER(creations.name) LIKE :query OR UPPER(creations.story) LIKE :query", { query: "%#{query.upcase}%" }]) }
+  scope :sorted_by, ->(direction) do
+    order(created_at: "oldest" == direction ? :asc : :desc)
+  end
 
   class Repository < SimpleDelegator
     def initialize(connection = Creation)
@@ -19,7 +22,7 @@ class Creation
     end
 
     def search_with(params)
-      all_matching(search_filters_for(params))
+      filter_by(search_filters_for(params))
     end
 
     private
@@ -27,17 +30,23 @@ class Creation
     attr_reader :connection
 
     def search_filters_for(params)
-      [
-        ->(cakes) { cakes.published },
-        ->(cakes) { params[:category].blank? ? cakes.all : cakes.where(category: Category.by_slug(params[:category])) },
-        ->(cakes) { params[:q].blank? ? cakes.all : cakes.search(params[:q]) },
-        ->(cakes) { cakes.order(created_at: sort(params)) },
-        ->(cakes) { params[:tags].blank? ? cakes.all : cakes.tagged(params[:tags].downcase.parameterize) },
-      ]
-    end
-
-    def sort(params)
-      params[:sort] == "oldest" ? :asc : :desc
+      query_builder_for(params) do |builder|
+        builder.always do |relation|
+          relation.published
+        end
+        builder.if_present(:category) do |relation, category|
+          relation.where(category: Category.by_slug(category))
+        end
+        builder.if_present(:q) do |relation, query|
+          relation.search(query)
+        end
+        builder.if_present(:tags) do |relation, tags|
+          relation.tagged(tags.downcase.parameterize)
+        end
+        builder.if_present(:sort) do |relation, sort_order|
+          relation.sorted_by(sort_order)
+        end
+      end
     end
   end
 end
app/models/creation.rb
@@ -9,8 +9,6 @@ class Creation < ActiveRecord::Base
   acts_as_taggable_on :tags
   alias_method :author, :user
 
-  default_scope -> { order(created_at: :desc) }
-
   def to_param
     "#{id}-#{name.parameterize}"
   end
spec/controllers/cakes_controller_spec.rb
@@ -3,49 +3,63 @@ require 'rails_helper'
 describe CakesController do
   let(:user) { create(:user) }
 
-  before(:each) do
-    photo = 'spec/fixtures/images/example.png'
-    cake.photos.create(image: photo)
+  before :each do
+    #photo = 'spec/fixtures/images/example.png'
+    #cake.photos.create(image: photo)
   end
 
   describe "#index" do
     let!(:cakes) { create(:category, slug: "cakes") }
     let!(:cookies) { create(:category, slug: "cookies") }
-    let!(:cake) { create(:published_cake, name: "cake", category: cakes) }
-    let!(:cookie) do
-      create(:published_cake, name: "cookie", category: cookies)
-    end
-
-    let!(:unpublished_cake) do
-      create(:cake, name: "unpublished", category: cakes)
-    end
 
     it "returns all published cakes" do
+      cake = create(:published_cake, category: cakes)
+      cookie = create(:published_cake, category: cookies)
+      unpublished_cake = create(:cake, category: cakes)
       get :index
       expect(assigns(:cakes)).to match_array([cake, cookie])
     end
 
     it "returns all cakes in the category" do
+      cookie = create(:published_cake, name: "cookie", category: cookies)
       get :index, category: cookie.category.slug
       expect(assigns(:cakes)).to match_array([cookie])
     end
 
     it "returns all cakes matching the search query" do
+      cake = create(:published_cake, name: "cake", category: cakes)
       get :index, q: cake.name[0..2]
       expect(assigns(:cakes)).to match_array([cake])
     end
 
     it "returns all cakes tagged with the tag" do
+      cake = create(:published_cake, name: "cake", category: cakes)
       cake.tag_list = "cakes"
       cake.save!
 
       get :index, tags: "cakes"
       expect(assigns(:cakes)).to match_array([cake])
     end
+
+    describe "sorting" do
+      let!(:old_cake) { create(:published_cake, name: 'old', created_at: 2.days.ago) }
+      let!(:new_cake) { create(:published_cake, name: 'new', created_at: 1.day.ago) }
+
+      it 'returns oldest cakes first' do
+        get :index, sort: "oldest"
+        expect(assigns(:cakes).to_a).to eql([ old_cake, new_cake ])
+      end
+
+      it 'returns newest cakes first' do
+        get :index, sort: "newest"
+        expect(assigns(:cakes).to_a).to eql([ new_cake, old_cake ])
+      end
+    end
   end
 
   describe "#show" do
     let(:cake) { create(:cake, user: user) }
+    let(:photo) { create(:photo, imageable: cake) }
 
     it "loads the cake" do
       get :show, id: cake.id
@@ -53,7 +67,6 @@ describe CakesController do
     end
 
     it 'loads the selected image' do
-      photo = cake.photos.first
       get :show, id: cake.id, photo_id: photo.id
       expect(assigns(:primary_image)).to eql(photo)
     end