Getting search values by clicking links in application layout - ruby-on-rails

I have a row of cities at the top of a layout. I also have a sidebar with a list of categories. In a view that is rendered within this layout, I'd like to query results of a model with category and location fields, based on which city has been selected, and by which category is most recently clicked.
I'm having trouble figuring out the best way to do this. I guess I'd like the city buttons to function like radio buttons, so that when a category is clicked, it is still using the last selected location to query the results. I'm also not sure how to pass the clicked button's value to the Offers model's search function:
def self.search(query, query2, query3)
where("title like ?", "%#{query}%").
where("category = ?", "%#{query2}%").
where("city = ?", "%#{query3}%")
end
This is the last step in finishing a large project for me, and any help would be greatly appreciated. If all else fails, I'll just end up using two dropdowns for category and location, but I'd really like to get this working.

Routing
You'll basically need to define a route to handle this - allowing you to pass a series of parameters through your links to your controller, allowing you bring back the data as defined by the links
Whether you handle this through plain HTTP, or through Javascript, it's the same process link > controller > response
I would do this:
#config/routes.rb
resources :offers do
collection do
get :search
end
end
This will allow you to create your links as follows:
<% #cities.each do |city| %>
<%= link_to city.name, offers_search_path(city: city.name) %>
<% end %>
This should create the link: domain.com/offers/search?city=scarborough
--
Controller
When you have a link like the one above, your controller will then be able to handle the city param, allowing you to perform a conditional search as follows:
#app/controllers/offers_controller.rb
Class OffersController < ApplicationController
def search
#result = Offer.search params[:city] #-> considering you want to use a class method
end
end
--
Ajax
If you wanted to perform the search via ajax, you'd do all of the above except you'd handle the link as a remote one:
<%= link_to city.name, offers_search_path(city: city.name), remote: true %>
This will send an ajaxified request to your controller, which you'll be able to handle as follows:
#app/controllers/offers_controller.rb
Class OffersController < ApplicationController
respond_to :js, :json, :html
def search
#result = Offer.search params[:city]
respond_with #result
end
end

Related

Rails: Using scopes to show different subsets of a model and how to build this the less DRY-ist way?

I have a model movies in my Ruby on Rails application and I want to have a few easy links that show different subsets of it - for example, movies not seen, movies that do not have a production year entered, movies rated as "great" etc.
I have created scopes for all of these conditions in my movie model, e. g.
scope :no_year, -> { where(release_year: [0, nil, ""]) }
But then I want to have a list of these subsets - as said, a list of links where user can click and will get the results in a default view (the movie index view actually). So I have a list of links such as the below, which required me to add routes as well as methods for all of these scopes that look pretty much the same.
<%= link_to 'Movies without a Year', noyear_movies_path %>
(<%= Movie.no_year.count %>)
routes.rb:
resources :movies do
get :noyear, on: :collection
end
movies_controller.rb:
def noyear
#q = Movie.no_year.ransack(params[:q]) # using Ransack for a sidebar that is displayed
#pagy, #movies = pagy(#q.result(distinct: true)) # using pagy to split up results
render 'index'
end
EDIT: added index and sidebar method code.
My index method looks like this:
def index
#pagy, #movies = pagy(#q.result(distinct: true))
end
... and the variable q itself is set application wide, as it is defined in a sidebar that contains a little search field that is always displayed:
def sidebar_q_movie
#q = Movie.ransack(params[:q])
end
I am sure this can be achieved way nicer. But since I am still pretty new to Ruby, I don't know that. Any hints appreciated!
Rather than having separate routes for each movie filter, you could specify the filter with a query param to Movies#index
<%= link_to 'Movies without a Year', movies_path(filter: 'no_year') %>
and in the controller:
def index
#movies = Movies.send(params[:filter])
end
Of course, you would want to validate the filter param first before sending to Movies.

Render results on #show without storing data in ActiveStorage

I'm learning RoR by building my first app (yay!). I gotta a question thought as rails guides do not cover this topic:
How to render unique results on #show to a user without storing any data in a model?
Steps I want to take:
Create a basic index view with a form_tag that will allow user to submit a link (string) and click submit button
Write Service Objects that will allow me to parse that link and create a response I want user to see
I want to write a #show method in a separate controller that will allow me to display all the data. (I also want to parse my params[:link] in that method using Service Objects.
I want to finally display this data in a table in #show view (probably I need to create a unique #show/[:id] for each user?
Here's what my app looks like at the moment (more or less):
Static Controller (just to render index.html.erb with a form)
class StaticController < ApplicationController
def index
end
end
Static Index view (yup, parsing imgur link here)
<h1>Hello Rails!</h1>
<%= form_tag("/images", method: "post") do %>
<p>
<%= label_tag(:imgur_link) %><br>
<%= text_field_tag(:imgur) %>
</p>
<p>
<%= submit_tag("Get my cards") %>
</p>
<% end %>
Images Controller (where all the magic SHOULD happen)
class ImagesController < ApplicationController
def show
#collection = params[:imgur_link]
#service1 = service1.new(*args).call
#service2 = service2.new(*args).call
...
end
end
Images Show view
Empty as I'm stuck with the Images controller at the moment.
Any help would be more than appreciated.
Thanks!
There is no reason you should put something into storage just in order to display it. If you get to a point when you have the results in your controller, you could just pass them to view in some #variable
As I see, you have set up the form for step 1. If you also have routes.rb call 'images#show' for POST /images, then you will have params[:imgur_link] available in your show action. This should do:
# config/routes.rb
YourApplication.routes.draw do
# ...
post '/images' => 'images#show'
end
Now you have to somehow process that link. Since I don't know what your results should be, I'm going to assume that you have two classes, Service1 and Service2, both of which accept an URL and return collection of results, and both collections hold the elements of the same class. Then you can leave only unique results for your show view, like this:
# app/controllers/images_controller.rb
class ImagesController < ApplicationController
def show
link = params[:imgur_link]
results1 = Service1.new(link).results
results2 = Service2.new(link).results
#results = (results1 + results2).uniq
end
end
Then you can do something with #results in your show view. E.g.
# app/views/images/show.html.erb
<% #results.each do |result| %>
<%= result.inspect %>
<% end %>

How do I use a button to update the order of a filtered category?

I am new to Rails, but slowly making progress. I can't quite wrap my head around how to achieve my next task.
I have a controller (IdeasController) with an index that looks like this:
def index
if params[:round].blank? && params[:challenge].blank?
#ideas = Idea.all.order(params[:sort])
# #ideas = Idea.all.order(created_at: :desc, cached_votes_up: :desc)
end
if params[:round].present?
#round_id = Round.find_by(name: params[:round]).id
#ideas = Idea.where(round_id: #round_id).order("created_at DESC")
end
if params[:challenge].present?
#challenge_id = Challenge.find_by(name: params[:challenge]).id
#ideas = Idea.where(challenge_id: #challenge_id).order("created_at DESC")
end
end
I am updating the view and filtering by category with the above :round and :challenge with the code below in my index.html.erb:
<%= link_to "All", ideas_path %>
<% Round.all.each do |round| %>
<%= link_to round.name, ideas_path(round: round.name) %>
<% end %>
<% Challenge.all.each do |challenge| %>
<%= link_to challenge.name, ideas_path(challenge: challenge.name) %>
<% end %>
Now, my problem is that I want to create a button that orders by created_at DESC or ASC. I want the button to essentially be a toggle. I also want another button to order by cached_weighted_average DESC or ASC. This is from acts_as_votable so I can sort by vote counts.
The problem I am running into is that I can create a link or button that orders by created_at or cached_weighted_average, but it replaces all of the URL that was previously filtered by :round or :challenge. For example, if a user clicks "Round 1" and sees all ideas marked for "Round 1" and then they click the link to order by cached_weighted_average, the URL replaces:
/ideas?round=Round+1
With this:
/ideas?sort=cached_weighted_average+ASC
What I want is:
/ideas?round=Round+1&?sort=cached_weighted_average+ASC
I know this is a very new question, but everything I have tried has failed so far. It feels like I am missing something very easy. What I noticed I can do easily is inside the controller I can do something like:
if params[:round].present?
#round_id = Round.find_by(name: params[:round]).id
#ideas = Idea.where(round_id: #round_id).order("cached_weighted_average DESC")
end
Which is perfect. This button just needs to switch between cached_weighted_average DESC and created_at DESC.
Any help is appreciated, thanks.
passing multiple parameters is one way to handle:
<%= link_to object.name, object_path(first: something, second: something_else) %>
then alter your conditionals to contemplate presence of multiple params.
to differentiate between round and challenge when attempting to allow the user to choose how they'd like to sort you could use the same name and then pass it different values.
something like:
params["round_or_challenge"]
this would change your conditional to something like:
if params["round_or_challenge"] == "round" && params["asc_or_desc"] == "asc"
# query
elsif params["round_or_challenge"] == "challenge"
# query
end
or whatever. it's basically the same...just pass the values you need. you can also pass the existing parameters from the view the same way you access them in the controller.
Thanks for the response, #toddmetheny. I didn't implement your solution, but your solution helped me understand passing multiple parameters a bit more.
I ended up creating a helper, sortable. I also used the url_for to append at the end of whatever the current URL might be. I liked this approach because it meant I could sort on any parameter. I'm not sure that it's the best solution, but it works.
def sortable (name, sort)
link_to name, url_for(params.merge(sort: sort))
end

Rails multiple records select and apply multiple actions

I am quite new at rails and I am having some trouble designing an admin dashboard.
What I want to achieve is this:
Have a list of multiple users from database.
Have the ability to select multiple records.
Have the ability to apply different actions to all of the
selected records.
The actions MAY not be directly translatable into SQL queries. (for example send an email)
I am not looking for a complete solution to the problem just a general description on how to approach this. I have a feeling I started on a wrong path.
So far I am doing this:
view
<%= form_tag("some_path", method: "get") do %>
<% #users.each do |user| %>
<%= check_box_tag "user_ids[]", users.id %>
<%end%>
<%= submit_tag("Send Email") %>
<%end%>
controller
def send_email
#recipients = User.find(params[:user_ids])
#recipients.each do |recipient|
Notifier.raw_email(recipient.email, params[:user_email][:subject], params[:user_email][:body]).deliver
end
end
This works as it is but i can only apply one action, send email that is.
I want to be able to choose an action to apply to all selected records or apply multiple actions to the selected records
Any thoughts?
You can use the send method to call methods of the model.
class User
def send_email(subject, body)
Notifier.raw_email(self.email, subject, body).deliver
end
end
Let /some_path also accept an array of actions
In our case actions = ['send_email']
In the action that some_path resolves to,
class SomeController < ActionController::Base
def some_action # that some_path resolves to in your config/routes.rb
#recipients = User.find(params[:user_ids])
#actions = params[:actions]
#recipients.each do |recipient|
#actions.each do |action|
recipient.send(action, params[:subject], params[:body])
end
end
end
end
In this way you can call multiple methods. Make sure you only accept valid action values or else the admin can simply call any of the User's methods.
You can have a select tag with the different actions you need.
Then on change of the select tag, you can update the action attribute of the form. eg, using jQuery.
$('#my-action-select').change(function() {
$('#myform').attr('action', $(this).val)
})

Creating a search input with Ruby on Rails

I am attempting to create a search form, the search is performed on third party site so there is no model just a controller. I have it set up as follows
views/items/index.html.haml :
= search_field :search, :search_input
= submit_tag 'Search'
views/items/search.html.haml:
-#items.each do |item|
%h1= item.title
controllers/items_controller.rb:
def index
#I am unsure of what to put in here? I think
#I need something wich sends #search_input to my search method
end
def search
#items = some_third_party_search_method_i_wrote{ params[:search_input]}
end
How does one properly use the params object in rails? I don't understand how to get from the index page containing the search form to the search page containing the results of the search input?
You probably want to use search_field_tag instead since your form isn't tied to a model/object:
= search_field_tag :search_input
Then params[:search_input] should work.

Resources