Search code examples
ruby-on-railsrspec-rails

Write Controller and Model Ruby Code Based On Specified Specs


I have to write Ruby code based on provided spec and make the tests specified in the spec pass when I run the code. The provided spec is below:

describe Product, type: :model do
  let!(:product) { Product.create(name: 'Hammer', category: 'Tools', price: 10.99) }

  describe ".search" do
    subject { Product.search(term) }

    context "has result" do
      context "term in name" do
        let(:term) { "hammer" }
        it { is_expected.to eq([product]) }
      end

      context "term in category" do
        let(:term) { "tool" }
        it { is_expected.to eq([product]) }
      end
    end
    
    context "has no result" do
      let(:term) { "nail" }
      it { is_expected.to eq([]) }
    end
  end
end

describe ProductsController, type: :controller do
  let!(:product) { Product.create(name: 'Blue shoes', category: 'Footwear', price: 10.99) }
  let!(:response) { RequestHelpers.execute('GET', ProductsController, 'search', {term: 'Shoe', format: :json}) }

  describe "#search" do
    it "returns json" do
      first_product = JSON.parse(response.body).first
      expect(first_product['name']).to eq 'Blue shoes' 
      expect(first_product['category']).to eq 'Footwear'
      expect(first_product['price']).to eq '10.99'
    end
  end
end

I have written the code below:

class ProductsController < ActionController::Base
  def create
    @product = Product.new(product_params)
    render json: @product
  end
  
  def search
    @products = Product.search(params[:search])
    render json: @products
  end
  
  private
  def product_params
    params.require(:product).permit(:name, :category, :price)
  end
end

class Product < ActiveRecord::Base
  validates_presence_of :name, :category, :price
  
  def self.search(search)
    where('name LIKE ?', "%#{search}%")
  end
  
end

I am expecting all the tests to pass

However, when I run the code, I get the stack trace below:

Test Results:
 Log
-- create_table(:products)
   -> 0.0047s
 Product
 .search
 has result
 term in name
 example at ./spec.rb:10
 term in category
 example at ./spec.rb:15
Test Failed
expected: [#<Product id: 1, name: "Hammer", category: "Tools", price: 0.1099e2, created_at: "2019-04-12 19:01:03", updated_at: "2019-04-12 19:01:03">]
     got: #<ActiveRecord::Relation []>

(compared using ==)

Diff:
@@ -1,2 +1,2 @@
-[#<Product id: 1, name: "Hammer", category: "Tools", price: 0.1099e2, created_at: "2019-04-12 19:01:03", updated_at: "2019-04-12 19:01:03">]
+[]
 has no result
 example at ./spec.rb:21
 ProductsController
 #search
 returns json

Where could I be going wrong in my code implementation?


Solution

  • Your syntax:

    find(:all, :conditions => ['name LIKE ?', "%#{search}%"])
    

    seems to be old/deprecated (see this Q&A). Instead, perhaps, try something like:

    class Product < ActiveRecord::Base
      validates_presence_of :name, :category, :price
    
      def self.search(search)
        if search
          where('name LIKE ?', "%#{search}%")
        else
          all
        end
      end
    
    end
    

    That where syntax may or may not need some fiddling as I didn't test it. Someone will likely point out if it is in error.

    Also, you define the search method as:

    def self.search(search)
      ...
    end
    

    which means that the search argument is required. But, then you test for the presence of search, here:

    if search
      where('name LIKE ?', "%#{search}%")
    else
      all
    end
    

    which doesn't really make sense since search is required. You should either (1) remove the if condition or (2) make search optional by doing:

    def self.search(search=nil)
      ...
    end
    

    Based on your edit, your "term in category" test is failing because you're only searching the name field, here:

    where('name LIKE ?', "%#{search}%")
    

    You don't have a product with a name like "tool", so your result is return an empty result set - which is what the error is telling you.

    Try something more like:

    where('name LIKE :search OR category LIKE :search', search: "%#{search}%")
    

    Again, you might have to fiddle with that syntax.