I am having a hard time understanding search in rails. I want to send search term from my view to service. I have this code in my service:
class Searching
def search_term
drive_auth.list_files(q: "fullText contains 'term'",
spaces: 'drive',
fields: 'nextPageToken, items(id, title)')
end
end
And this code in controller:
class SearchController < ApplicationController
def index; end
def show
//here I should take term from search box and send to
search_term in service instead of word 'term'
end
end
And this is my view (I understand it's not full code, that's why I need help)
<p>
<%= text_field_tag :search, params[:search] %>
<%= submit_tag "Search", :name => nil %>
</p>
What I want is to search from index.html.erb and show results in show.html.erb page on click. How to send this parametar to search method in service and show results in html?
If you want to send params[:search] to from your controller to service, below code may solve your issue:
In controller:
class SearchController < ApplicationController
def index; end
def show
result = Searching.new.search_term(params[:search])
# Your code to use result
end
end
In service:
class Searching
def search_term(q)
drive_auth.list_files(q: "fullText contains '#{q}'",
spaces: 'drive',
fields: 'nextPageToken, items(id, title)')
end
end
Related
I'm creating a simple Rails app that fetches data from the Open Weather Map API and returns the current weather data of the city that is searched for in a form field. I would like an API call to look like this for example:
http://api.openweathermap.org/data/2.5/weather?q=berlin&APPID=111111
I've tested this in Postman with my API key it works fine but with my code it returns "cod":"400","message":"Nothing to geocode"
Can anyone see where I am going wrong? Here is my code.
services/open_weather_api.rb
class OpenWeatherApi
include HTTParty
base_uri "http://api.openweathermap.org"
def initialize(city = "Berlin,DE", appid = "111111")
#options = { query: { q: city, APPID: appid } }
end
def my_location_forecast
self.class.get("/data/2.5/weather", #options)
end
end
forecasts_controller.rb
class ForecastsController < ApplicationController
def current_weather
#forecast = OpenWeatherApi.new(#options).my_location_forecast
end
end
current_weather.html.erb
<%= form_tag(current_weather_forecasts_path, method: :get) do %>
<%= text_field_tag(:q) %>
<%= submit_tag("Search") %>
<% end %><br>
<%= #forecast %>
routes.rb
Rails.application.routes.draw do
root 'forecasts#current_weather'
resources :forecasts do
collection do
get :current_weather
end
end
end
The error describes itself:
"cod":"400","message":"Nothing to geocode"
it means that you didn't provide it the city in your query. One possible cause of this error is that you are overriding the default value in your initialize method with the #options variable from the controller in this line:
class ForecastsController < ApplicationController
def current_weather
#forecast = OpenWeatherApi.new(#options).my_location_forecast
end
end
From the information you provided, you've not defined the #options variable in your controller or it is nil. So this is overriding the default value of the initialize method in OpenWeatherApi .
Since the appid in your case will not change, only the city name will change so you can send it from controller.
def current_weather
#city = params[:city] // the city you want to send to API. Change it with your value
#forecast = OpenWeatherApi.new(#city).my_location_forecast
end
I have something like that in my controller:
def index
#votes = Vote.all
end
private
def search
#votes = OtherVotes.all
end
I want to use search method in index action but I don't want to remove my #votes variable from index. If I use before_action, it calls method before the action so #votes doesn't change. Is it possible to call search method after my votes variable or ignore the variable without removing.
I normally go with this method when I'm looking to build a simple search:
http://railscasts.com/episodes/37-simple-search-form
Create a method in your vote.rb file:
class Vote
def self.search(search)
if search
self.where(:all, conditions: ['name LIKE ?', "%#{search}%"])
else
self.where(:all)
end
end
end
This means when you do Vote.search('term'), you'll bring up any records with a similair name. Replace name for whatever term you're searching for (i.e. title or category).
If there is no search term entered this method simply returns every instance. This means you can leave your controller looking like this:
def index
#votes = Vote.search(params[:search])
end
Finally the view for this would be something like:
<% form_tag votes_path, :method => 'get' do %>
<p>
<%= text_field_tag :search, params[:search] %>
<%= submit_tag "Search", :name => nil %>
</p>
<% end %>
This will send a get request to the votes_path (the index action on your controller), with the search term parameter. If one is entered the search will return the relevant instances, and if not it will return all.
Try
class TempController < ApplicationController
after_action :search
def index
#votes = Vote.all
end
private
def search
#votes = OtherVotes.all
end
end
I'm very new to Ruby on Rails and trying to create a search function that allows the user to serach multiple parameters at the same time; from, and to. Something to keep in mind is that there will probably be even more parameters later on in the development. I've got it to work when searching for one of the fields, but not more than that.
Search view:
<%= form_tag(journeys_path, :method => "get", from: "search-form") do %>
<%= text_field_tag :search_from, params[:search_from], placeholder: "Search from", :class => 'input' %>
<%= text_field_tag :search_to, params[:search_to], placeholder: "Search to", :class => 'input' %>
<%= submit_tag "Search", :class => 'submit' %>
<% end %>
Method:
class Journey < ActiveRecord::Base
def self.search(search_from)
self.where("from_place LIKE ?", "%#{search_from}%")
end
end
Controller:
class JourneysController < ApplicationController
def index
#journeys = Journey.all
if params[:search_from]
#journeys = Journey.search(params[:search_from])
else
#journeys = Journey.all.order('created_at DESC')
end
end
def search
#journeys = Journey.search(params[:search_from])
end
end
I've tried some gems and all kind of solutions that I've found in other questions, but I'm just not good enough at RoR yet to succesfully apply them correctly without help. I would appreciate any help I can get.
Thank you!
Model:
class Journey < ActiveRecord::Base
def self.search(search_from, search_to)
self.where("from_place LIKE ? and to_place LIKE ?", "%#{search_from}%", "%#{search_to}%")
end
end
Controller:
class JourneysController < ApplicationController
def index
if params[:search_from] and params[:search_to]
#journeys = search
else
#journeys = Journey.all.order('created_at DESC')
end
end
def search
#journeys = Journey.search(params[:search_from], params[:search_to])
end
end
The best approach here is to incapsulate your search form as a separate Ruby class. Using Virtus here helps to get type coercion for free.
class SearchForm
include Virtus.model # Our virtus module
include ActiveModel::Model # To get ActiveRecord-like behaviour for free.
attribute :from, String
attribute :to, String
# Just to check if any search param present,
# you could substitute this with validations and just call valid?
def present?
attributes.values.any?{|value| value.present?}
end
```
In Rails 3 IIRC you also have to include ActiveModel::Validations to be able to validate your form input if needed.
Now, let's see how to refactor controller. We instantiate form object from params and pass that to the model query method to fetch records needed. I also moved ordering out of if clause and used symbol ordering param - cleaner IMO.
def index
#search_form = SearchForm.new(search_params)
if #search_form.valid? && #search_form.present?
#journeys = Journey.search(#search_form)
else
#journeys = Journey.all
end
#journeys = #journeys.order(created_at: :desc)
end
def search
#journeys = Journey.search(SearchForm.new(search_params)
end
private
def search_params
params.require(:search_form).permit(:from, :to)
end
Now to the view: form_for will work perfectly with our form object, as will simple_form_for
<%= form_for #search_form, url: journeys_path, method: :get do |f| %>
<%= f.text_field :from, placeholder: "Search from", class: 'input' %>
<%= f.text_field :to, placeholder: "Search to", class: 'input' %>
<%= f.submit "Search", class: 'submit' %>
<% end %>
View looks now much shorter and cleaner. Incapsulating params in object makes working with search params muuuuch easier.
Model:
class Journey < ActiveRecord::Base
def self.search(search_form)
if search_form.kind_of?(SearchForm)
journeys = all # I'm calling Journey.all here to build ActiveRecord::Relation object
if search_form.from.present?
journeys = journeys.where("from_place LIKE ?", "%#{search_form.from}%")
end
if search_form.to.present?
journeys = journeys.where("to_place LIKE ?", "%#{search_form.to}%")
end
else
raise ArgumentError, 'You should pass SearchForm instance to Journey.search method'
end
end
end
Notice how I build ActiveRecord::Relation object by calling Journeys.all and applying each search param if present. Chaining where like that would put AND in between automatically, if you need OR Rails 4 has it: Journey.or(condition).
Pros of this approach:
You are using Plain Old Ruby Classes, almost no magic here, and it works like usual Rails model in many ways. Putting search params in the object makes it a lot easier to refactor code. Virtus is the only dependency, sans Rails itself of course, and it's more for convenience and to avoid writing boring boiler-plate code.
You can easily validate input if needed (If you really want to be strict about input and show user validation error instead of silently executing stupid query with contradicting conditions and returning no results).
My show action:
def show
# Multiple keywords
if current_user.admin?
#integration = Integration.find(params[:id])
else
#integration = current_user.integrations.find(params[:id])
end
#q = #integration.profiles.search(search_params)
#profiles = #q.result.where(found: true).select("profiles.*").group("profiles.id, profiles.email").includes(:integration_profiles).order("CAST( translate(meta_data -> '#{params[:sort_by]}', ',', '') AS INT) DESC NULLS LAST").page(params[:page]).per_page(20)
#profiles = #profiles.limit(params[:limit]) if params[:limit]
end
There can be many different filters taking place in here whether with Ransacker, with the params[:limit] or others. At the end I have a subset of profiles.
Now I want to tag all these profiles that are a result of the search query.
Profiles model:
def self.tagging_profiles
#Some code
end
I'd like to create an action within the same controller as the show that will execute the self.tagging_profiles function on the #profiles from the show action given those profiles have been filtered down.
def tagging
#profiles.tagging_profiles
end
I want the user to be able to make a search query, have profiles in the view then if satisfied tag all of them, so there would be a need of a form
UPDATE:
This is how I got around it, don't know how clean it is but here:
def show
# Multiple keywords
if current_user.admin?
#integration = Integration.find(params[:id])
else
#integration = current_user.integrations.find(params[:id])
end
#q = #integration.profiles.search(search_params)
#profiles = #q.result.where(found: true).select("profiles.*").group("profiles.id, profiles.email").includes(:integration_profiles).order("CAST( translate(meta_data -> '#{params[:sort_by]}', ',', '') AS INT) DESC NULLS LAST").page(params[:page]).per_page(20)
#profiles = #profiles.limit(params[:limit]) if params[:limit]
tag_profiles(params[:tag_names]) if params[:tag_names]
end
private
def tag_profiles(names)
#profiles.tagging_profiles
end
In my view, I created a form calling to self:
<%= form_tag(params.merge( :controller => "integrations", :action => "show" ), method: :get) do %>
<%= text_field_tag :tag_names %>
<%= submit_tag "Search", class: "btn btn-default"%>
<% end %>
Is this the best way to do it?
Rails public controller actions correspond always to a http request. But here there is just no need for 2 http requests. A simple solution would be just creating to private controllers methods filter_profiles(params) and tag_profiles(profiles) and just call them sequentially.
You can also extract this problem entirely to a ServiceObject, like this:
class ProfileTagger
attr_reader :search_params
def initialize(search_params)
#search_params = search_params
end
def perform
search
tag
end
def tag
#tag found profiles
end
def search
#profiles = #do the search
end
end
As processing 30,000 records is a time consuming operation, it would make sence to perform it outside of the rails request in background. This structure will allow you to delegate this operation to a sidekiq or delayed_job worker with ease
Instance Variables
If you want to "share" variable data between controller actions, you'll want to look at the role #instance variables play.
An instance of a class means that when you send a request, you'll have access to the #instance variable as long as you're within that instance of the class, I.E:
#app/controllers/your_controller.rb
Class YourController < ApplicationController
before_action :create_your_var
def your_controller
puts #var
end
private
def create_your_var
#var = "Hello World"
end
end
This means if you wish to use the data within your controller, I would just set #instance variables, which you will then be able to access with as many different actions as you wish
--
Instance Methods
The difference will be through how you call those actions -
#app/controllers/your_controller.rb
Class YourController < ApplicationController
def action
#-> your request resolves here
method #-> calls the relevant instance method
end
private
def method
#-> this can be called within the instance of the class
end
end
I'm new to developing in Ruby on Rails and I'm stuck on a little project that I've been working on to understand RoR better. I am trying to make a little weather website and I'm having trouble sending user input to a model through a controller and to have that model use send back the correct information so that I can parse it and what not. I have not been able so far to send the user param along to the controller so that it will send out the right request. Here is my following code:
hourly.html.erb:
<%= form_tag('/show') do %>
<%= label_tag(:location, "Enter in your city name:") %>
<%= text_field_tag(:location) %>
<br />
<%= submit_tag "Check It Out!", class: 'btn btn-primary' %>
<% end %>
hourly_lookup_controller.rb:
class HourlyLookupController < ApplicationController
def show
#hourly_lookup = HourlyLookup.new(params[:location])
end
end
hourly_lookup.rb:
class HourlyLookup
def fetch_weather
HTTParty.get("http://api.wunderground.com/api/api-key/hourly/q/CA/#{:location}.xml")
end
def initialize
weather_hash = fetch_weather
assign_values(weather_hash)
end
def assign_values(weather_hash)
more code....
end
end
Any help or directions to some good examples or tutorials would be greatly appreciated. Thanks
If you want to send a variable to HourlyLookup, you'll need to do so:
class HourlyLookupController < ApplicationController
def show
#hourly_lookup = HourlyLookup.new(params[:location])
#hourly_lookup.fetch_weather
end
end
class HourlyLookup
attr_reader :location
def initialize(location)
#location = location
end
def fetch_weather
response = HTTParty.get("http://api.wunderground.com/api/cdb75d07a23ad227/hourly/q/CA/#{location}.xml")
parse_response(response)
end
def parse_response(response)
#parse the things
end
end