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 %>
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:
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
.
Use ruby -c filename
to check for syntax errors. First thing to try when you get errors on load.
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
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
.