I'm trying to test the behavior of a custom search method in my controller:
#RecordingsController
def search
# raise params.inspect
#search = params[:search]
searches = []
searches2 = []
for n in 1..5
searches << #search["date(#{n}i)"].to_i
searches2 << #search["date2(#{n}i)"].to_i
end
start_date = date_format(*searches)
end_date = date_format(*searches2)
conditions = []
conditions << "agent like '%#{#search["agent"]}%'" unless #search["agent"].blank?
conditions << "phone like '%#{#search["phone"]}%'" unless #search["phone"].blank?
conditions << "date between '#{start_date}' and '#{end_date}'"
#recordings = Recording.where(conditions.join(" AND ")).order('date ASC')
if #recordings.blank?
redirect_to("/", alert: "No results were found for this search. Please try again.")
else
render "recordings/search"
end
end
using the following layout:
#recordings_controller_spec.rb
describe RecordingsController do
describe "POST #search" do
context "with valid attributes" do
it "assigns a new search to #search" do
search = #recording_search
get :search, #recording_search
assigns(:search).should eq(search)
end
it "populates an array of recordings"
it "renders the :search view"
end
end
end
The furthest I've gotten is trying to build a hash that mimics what my params hash would be for the form
#params hash
params = {"search" => { "date_1i" => "2012", "date_2i" => "1", ... "date2_5i" => "00" } }
where date_#{n}i is the start date [year, month, day, hour, minute], and date2_#{n}i is the end date. I'm trying to follow the answer posted here, mimicking the params hash with just a regular hash. As you can see from my controller, I don't actually pass parameters to my #search method. Should I be? Or is there a way to mock a params hash in an rspec test and determine if my #search, #recordings, and redirect_to/render variables/actions are being performed? I'm already kind of testing the render/redirect in my request spec, but I'd like to fully test this method if I can.
You should be able to generate a controller spec that GETs the search action with a given set of parameters. This will cause those parameters to be available to the params hash. You can then verify how the search is constructed and which results are returned.
describe RecordingsController do
describe '#search' do
it 'should return results' do
get :search, "search" => { "date_1i" => "2012", "date_2i" => "1", ... "date2_5i" => "00" }
response.should be_ok
#recordings.map(&:name).should == ['expected1', 'expected2']
end
end
end
This example executes a search with some search criteria as query parameters, verifies the response is successful (http 200 status), and then extracts the list of recordings returned and tries to map them to a list of friendly recording names (you can use any key on this model) to compare them to an expected list of results.
It'll be easier to write/test this code if you separate the concerns in this controller - you could write a helper that processes the query parameters and builds a search filter, which it then passes to the Recording model in the controller:
class RecordingController
def search
#search_filter = SearchFilter.for_params(params[:search])
#recordings = Recording.where(#search_filter).order('date ASC')
render "recordings/search"
end
end
class SearchFilter
# Return search params as a hash for given request params hash
def self.for_params(params)
...
end
end
This would let you write a unit test for the logic that generates search filters and only verify that the controller is doing the more simple operation of passing information between the search logic and the Recording model collection. I'd also recommend moving your logic about displaying empty results into the view on the results page and out of the controller.
Related
I want to filter jobs on the parameter passed onto the model, currently search works flawlessly without query passed into the model, but when I type query it doesn't return anything. How can I perform this query with query and criteria.
results << model.with_query(query).where(criteria). any idea would be really appreciated.
module Refinery
class SearchEngine
# How many results should we show per page
RESULTS_LIMIT = 100
# Perform search over the specified models
def self.search(query, job_region, job_division, country, job_type, page = 1)
results = []
Refinery.searchable_models.each do |model|
criteria = {:job_region => job_region,
:job_division => job_division,
:country => country,
:job_type => job_type
}.select { |key, value| value.present? }
if query.present?
results << model.with_query(query).where(criteria)
else
results << model.limit(RESULTS_LIMIT).where(criteria)
end
end
results.flatten[0..(RESULTS_LIMIT - 1)]
end
end
end
The problem here is that the method .with_query(qry) returns an Array. You want to do chain-scoping, so you must use scopes that returns ActiveRecord::Relation objects.
model.with_query(query)
# returns an Array
model.with_query(query).where(criteria)
# calling .where on an Array object => NoMethodError
model.where(criteria)
# returns an ActiveRecord::Relation
model.where(criteria).with_query(query)
# calls the query on an AR::Relation object, which is doable
Short version: Change this:
results << model.with_query(query).where(criteria)
To this:
results << model.where(criteria).with_query(query)
I have the following model and I want to pass multiple params in "with_query", don't know how to achieve it. currently you can see it takes only "query" param. how can I filter it with country and job_type. any help would be really appreciated.
search Model
def self.search(query, country, job_type, page = 1)
results = []
Refinery.searchable_models.each do |model|
results << model.limit(RESULTS_LIMIT).with_query(query)
end if query.present?
results.flatten[0..(RESULTS_LIMIT - 1)]
end
Controller
def show
#results = Refinery::SearchEngine.search(params[:query], params[:country], params[:job_type], params[:page])
present(#page = Refinery::Page.find_by_link_url("/search"))
end
I would try changing the line that builds your results in the search model to:
results << model.limit(RESULTS_LIMIT).with_query(query).where(country: country, job_type: job_type)
What I'm trying to do: I have a model "Recipe" in which I defined a method "search" that takes an array of strings from checkboxes (I call them tags), and a single string. The idea is to search the db for recipes that has anything in it's 'name' or 'instructions' that contains the string, AND also has any of the tags matching it's 'tags' property.
Problem: The search method return all the recipes in my db, and doesn't seem to work at all at finding by the specific parameters.
The action method in the controller:
def index
#recipes = Recipe.search(params[:search], params[:tag])
if !#recipes
#recipes = Recipe.all
end
respond_to do |format|
format.html
format.json { render json: #recipe }
end
end
The search method in my model:
def self.search(search, tags)
conditions = ""
search.present? do
# Condition 1: recipe.name OR instruction same as search?
conditions = "name LIKE ? OR instructions LIKE ?, '%#{search[0].strip}%', '%#{search[0].strip}%'"
# Condition 2: if tags included, any matching?
if !tags.empty?
tags.each do |tag|
conditions += "'AND tags LIKE ?', '%#{tag}%'"
end
end
end
# Hämtar och returnerar alla recipes där codition 1 och/eller 2 stämmer.
Recipe.find(:all, :conditions => [conditions]) unless conditions.length < 1
end
Any ideas why it return all records?
if you are using rails 3, then it is easy to chain find conditions
def self.search(string, tags)
klass = scoped
if string.present?
klass = klass.where('name LIKE ? OR instructions LIKE ?', "%#{string}%", "%#{string}%")
end
if tags.present?
tags.each do |tag|
klass = klass.where('tags LIKE ?', "%#{tag}%")
end
end
klass
end
When you do
search.present? do
...
end
The contents of that block are ignored - it's perfectly legal to pass a block to a function that doesn't expect one, however the block won't get called unless the functions decides to. As a result, none of your condition building code is executed. You probably meant
if search.present?
...
end
As jvnill points out, it is in general much nicer (and safer) to manipulate scopes than to build up SQL fragments by hand
I'm trying to chain criteria based on optional rails
parameters.
I want to be able to simultaneously filter based on selected tags as
well as searching.
Here is the current code that works in all situations:
if params[:tag] and params[:search]
#notes = Note.tagged_with_criteria(params[:tag]).full_text_search(params[:search])
elsif params[:tag]
#notes = Note.tagged_with_criteria(params[:tag])
elsif params[:search]
#notes = Note.full_text_search(params[:search])
end
I tried simply using
#notes = Note.tagged_with_criteria(params[:tag]).full_text_search(params[:search])
without the if statement, but then if only one of the params was
given, then all notes are returned.
Each of the methods (tagged_with_criteria and full_text_search) are
returning Note.criteria if their parameter is nil / empty.
Is there a simpler, more elegant way to chain Mongoid criteria like this?
I'd rather keep tacking on criteria one-by-one instead of having to do
the weird "if params[...] and params[...]" thing..
UPDATE: here are the current methods:
def tagged_with_criteria(_tags)
_tags = [_tags] unless _tags.is_a? Array
if _tags.empty?
criteria
else
criteria.in(:tags => _tags)
end
end
def self.full_text_search(query)
if query
begin
regex = /#{query}/
# supplied string is valid regex (without the forward slashes) - use it as such
criteria.where(:content => regex)
rescue
# not a valid regexp -treat as literal search string
criteria.where(:content => (/#{Regexp.escape(query)}/))
end
else
# show all notes if there's no search parameter
criteria
end
end
In a situation like that, I would modify the scopes to do nothing when passed in blank values.
I think what might be happening is you are getting empty strings from the params hash, which is causing your code to think that something was entered. Try the scopes with these edits.
def tagged_with_criteria(_tags)
_tags = Array.wrap(_tags).reject(&:blank?)
if _tags.empty?
criteria
else
criteria.in(:tags => _tags)
end
end
def self.full_text_search(query)
if query.present?
begin
regex = /#{query}/
# supplied string is valid regex (without the forward slashes) - use it as such
criteria.where(:content => regex)
rescue
# not a valid regexp -treat as literal search string
criteria.where(:content => (/#{Regexp.escape(query)}/))
end
else
# show all notes if there's no search parameter
criteria
end
end
Hey all, I am completely lost on this one.
I found a code snippet online to help validate fields via ajax as the user types into them. So I'm trying to write a spec against part of it and I just can't get it to pass.
Here's the code
def validate
field = params[:field]
user = User.new(field => params[:value])
output = ""
user.valid?
if user.errors[field] != nil
if user.errors[field].class == String
output = "#{field.titleize} #{user.errors[field]}"
else
output = "#{field.titleize} #{user.errors[field].to_sentence}"
end
end
render :text => output
end
and here is my test so far
describe "POST validate" do
it "retrieves the user based on the past in username" do
mock_errors ||= mock("errors")
mock_errors.stub!(:[]).and_return(nil)
User.should_receive(:new).with({'username'=>"UserName"}).and_return(mock_user)
mock_user.should_receive(:valid?).and_return(true)
mock_errors.should_receive(:[]).with("username").and_return(nil)
put :validate, :field=>'username', :value=>'UserName'
response.should == ""
end
end
I get this error -
1) Spec::Mocks::MockExpectationError
in 'UsersController POST validate
retrieves the user based on the past
in username' Mock 'errors' received
unexpected message :[] with
("username")
I can't seem to figure out how in the world to mock the call to user.errors[field]. Ideally this spec tests the happy path, no errors. I'll then write another for a validation failure.
I'm not seeing mock_user. Here's a shot at it:
describe "POST validate" do
it "retrieves the user based on the past in username" do
mock_errors = mock("errors")
mock_user = mock("user")
mock_user.stub!(:errors).and_return([mock_errors])
mock_errors.stub!(:[]).and_return(nil)
User.should_receive(:new).with({'username'=>"UserName"}).and_return(mock_user)
mock_user.should_receive(:valid?).and_return(true)
mock_errors.should_receive(:[]).with("username").and_return(ActiveRecord::Errors.new({}))
put :validate, :field=>'username', :value=>'UserName'
response.should == ""
end
end
The key is that you need your User mock to respond to the errors method by returning either an empty hash or a hash of fieldname/errors. An alternative to this is to use one of the fixture replacement tools. I'm using machinist right now, which might reduce this whole thing to:
describe "POST validate" do
it "retrieves the user based on the past in username" do
#user = User.make{'username'=>"UserName"}
#user.should_receive(:valid?).and_return(true)
#user.errors.should_receive(:[]).with("username").and_return(ActiveRecord::Errors.new({}))
put :validate, :field=>'username', :value=>'UserName'
response.should == ""
end
end