Connecting Search Form in Ruby with Sinatra

999 Views Asked by At

I have a search form I'm using with the YP (Yellow Pages) API, coded in Ruby, with Sinatra.

I've managed to connect all the dots getting the search to work on the back-end, but am having trouble connecting the search form to the API call. Everything displays properly on the page, but nothing shows up when I click the submit button.

The code I am using is below:

require 'rubygems'
require 'sinatra'
require 'yp'

# index.rb 

get "/" do
  # Only run the search if both of our params are available
  if params[:location] && params[:term]
  client = Yp::Client.new(api_key: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
  results = client.search(searchloc: params[:location], term: params[:term], listingcount: 1, sort: 'distance')
  erb :index, :locals => {results: results}
end

__END__

@@index

<h1>YP Search</h1>

<form action="/" method="get">  
  Location: <input type="text" name="location">  <br />
  Search Term: <input type="text" name="term" required> <br />
<input type="submit">  
</form> 

<% if results %>
 <% results.each do |result| %>
 # Print out the result info
<% end %>
<% else %>
 # They haven't searched yet.
<% end %>
1

There are 1 best solutions below

5
On BEST ANSWER

As you've written it, you're not passing results to the template. The last call in the block becomes the return value, which is the body passed to the client, so with your code:

get "/" do
  # Only run the search if both of our params are available
  if params[:location] && params[:term]
  client = Yp::Client.new(api_key: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
  @results = client.search(searchloc: params[:location], term: params[:term], listingcount: 1, sort: 'distance')
end

This line:

  @results = client.search(searchloc: params[:location], term: params[:term], listingcount: 1, sort: 'distance')

becomes the body. What you want is the rendered template to be the last line, so:

get "/" do
  # Only run the search if both of our params are available
  if params[:location] && params[:term]
  client = Yp::Client.new(api_key: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
  @results = client.search(searchloc: params[:location], term: params[:term], listingcount: 1, sort: 'distance')
  erb :index
end

Instance variables are automatically visible to templates so that's all you'd need to do. If you wanted to pass a local variable:

get "/" do
  # Only run the search if both of our params are available
  if params[:location] && params[:term]
  client = Yp::Client.new(api_key: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
  results = client.search(searchloc: params[:location], term: params[:term], listingcount: 1, sort: 'distance')
  erb :index, :locals => {results: results}
end

and then remove the @ from the front of the @results var in the template.

erb :index is a method call. It says "call the template renderer, use the ERB engine and pass it the :index template`. The return value of the method is a string. That string can then be used anywhere, just like a normal string, but you'd obviously be most likely to put it at the end of a route block, since you want to return a string.

Where you've placed it right now, in the class body, it's just a method call with a result that goes to nowhere. It's the same as doing:

get "/you/can/access/this" do
  "and this would be the returned body"
end

get "/and/this" do
  erb :index, :locals=>{:what_youd_see => "would be the template"}
end

"But this is just a string that no one can access, it gets called every time, but who is the receiver?"

Think of your Sinatra app like it's a class, and then the normal rules of Ruby become clearer to apply :)


I cloned your Git repo and found a few problems:

  • You don't need to list rubygems as a dependency in the gemspec.
  • Shotgun wasn't listed in the gemspec or Gemfile as a development dependency.
  • Rails was listed even though it's not in the project.

in the gemspec:

Gem::Specification.new do |gem|
  #other code
  gem.version       = Yp::VERSION
  gem.add_development_dependency "shotgun"
  gem.add_dependency('faraday', ["< 0.8", ">= 0.6"])    
  gem.add_dependency('faraday_middleware', [">= 0.8"])  
  gem.add_dependency('sinatra')  
end

Above is how I changed the yp.gemspec file, but personally I don't put development dependencies in the gemspec, but in the Gemfile.

# Gemfile

group :development do
  gem "shotgun"
end

I think it's easier to manage and separates things better. Instead of running bundle install I run bundle install --binstubs --path vendor as it puts everything in the local project directory. That way all projects are sandboxed from each other and you'll notice if you've missed anything out. To run the app I used bundle exec ruby index.rb.

  • There were syntax errors with index.rb

Use ruby -c filename to check for syntax errors. First thing to try when you get errors on load.

  • In Ruby you have to close if…else blocks with end.

I also added in a halt if the params aren't given, but you could also use an error handler or pass the the error into the template to inform the user. YMMV.

get "/" do
  halt 400, "You need to supply a location and a term"
  # Only run the search if both of our params are available
  if params[:location] && params[:term]
    client = Yp::Client.new(api_key: "e89cba4b974a122e408d1723626f3709")
    results = client.search(searchloc: params[:location], term: params[:term], listingcount: 1, sort: 'distance')
  end
  erb :index, :locals => {results: results}
end
  • You've hardcoded the API key.

I'm guessing this should be secret. Best thing is probably to get a new one, and never hardcode it and never check it in to Git. I use a file that's not tracked by Git and is loaded into the environment variables, e.g.

YAML.load_file("config/secret_settings.yml").each do |key,value|
  ENV[key.upcase] = value
end

I add this into the Rakefile and run the app from the rack file, e.g.

namespace :app do

  desc "Set up the environment locally"
  task :environment do
    warn "Entering :app:environment"
    YAML.load_file("config/secret_settings.yml").each do |key,value|
      ENV[key.upcase] = value
    end
  end
  
  desc "Run the app locally"
  task :run_local => "app:environment" do
    exec "bin/rackup config.ru -p 4567"
  end
end

from the commandline:

bin/rake app:run_local

and you'll probably want to add a config.ru.