Expand search functionality - ruby-on-rails

I am building a website which has multiple tables which detail features of the site such as car pooling, matches, opponents and registered users. I have added a search function which searches for matches on the website but I do not know how to expand upon this so a user can search for car pooling details, opponents and registered users. I could probably just repeat what I have done for matches over again but this seems inefficient and I am sure there must be a way to capture all matching information to the search term entered.
There is a fair bit of code so bear with me please -
This is in the matches_contoller.rb file:
def search
#search_term = params[:q]
st = "%#{params[:q]}%"
#matches = Match.where("Opponent like ?", st)
respond_to do |format|
format.html # index.html.erb
format.json { render json: #matches }
end
end
I have added a _search.html.erb to the matches folder with this code:
<%= form_tag("/search", :method => "post") do %>
<%= label_tag(:q, "Search for:") %>
<%= text_field_tag(:q) %>
<%= submit_tag("Search") %>
<% end %>
I have added a search.html.erb to the matches folder with this code:
<h1>Search Matches Catalog</h1>
<br />
<h3>Searching For: <%= #search_term %></h3>
<table class="catalog">
<% if #matches.length == 0 %>
<br />
<h2>No matches found for this search term!!</h2>
<% end %>
<% #matches.each do |match| %>
<tr>
<td><%= match.opponent %></td>
<td><%= match.game %></td>
<td><%= match.home_away %></td>
<td><%= match.kick_off.strftime("%d/%m/%Y at %I:%M") %></td>
<td><%= match.score %></td>
<td><%= match.result %></td>
</tr>
<% end %>
</table>
<% if session[:login] == 1 %>
<p><%= link_to 'New match', new_match_path %></p>
<% end %>
This has been added to the routes.rb file:
post '/search' => 'matches#search'
And finally this has been added to the application.html.erb file:
<div class="searchbox">
<%= render :partial => 'matches/search' %>
</div>
I realise this is a bit long winded and any advice is greatly appreciated.
If anyone is so enclined I am happy to share my cloud9 development environment for someone to take a look if they feel it would be easier.
Thanks in advance and I hope the question is clear.

I would advice you with elasticsearch, it has flexible and advanced search indices in which you can add the columns you want to search by which satisfies your requirements.
An awesome elasticsearch client is searckick. It abstracts all difficulties in elasticsearch DSL and simplifies your mission.
First you will need to install elasticsearch then add gem 'searchkick' to your Gemfile.
If you use a Mac
brew install elasticsearch
the same for Linux distributions just replace brew by apt-get in ubuntu, yum or dnf in Fedora.
Add searchkick to your model
class Match < ActiveRecord::Base
searchkick
end
To control indexed data use the method search_data
class Match < ActiveRecord::Base
searchkick
def search_data
{
opponents: opponents_data,
details: details
}
end
def opponents_data
self.opponents.pluck(:name).join(',')
end
end
After that run Match.reindex and you can use Match.search("query")
Read more about elasticsearch and searchkick in their respective documentations. (Links attached above)

Related

Rails /(year)/(month)/(day) separate pages

I'm very new to rails and am having some trouble. I have a model called BusinessDates that consists of two tables, calendar_date and seasonality (you can ignore seasonality). What I'm trying to achieve is to be able to move through them easily like folders.
It took me a few solid days of google-foo, but I was able to have the index display a list of each unique year in order as links with friendly_ids. From here I want to click on it and have it link to a new view that displays a list of each unique month in that particular year in order as links with friendly_ids. Then (as you could guess) have it display on another new view a list of all the days in the selected month in the selected year. I want the url to be business_dates/2016/5/21 or in other words business_dates/(year)/(month)/(day).
My issue: I don't know where to go from here. I can't even seem to find any info on making a second level deep non-static url without either making each year month and day separate models (want to avoid that), or what looks like a rube goldberg machine to kinda get there but without views for each page (You'd have to just type the full date into the url).
Please help a beginner who feels very lost!
Controller:
def index
#years = BusinessDate.pluck(:calendar_date).map{|x| x.year}.uniq
end
index.erb.html
<table>
<tr>
<th>Year</th>
</tr>
<% #years.sort.each do |year| %>
<tr>
<td><%= link_to year, business_date_path(year) %></td>
</tr>
<% end %>
</table>
You can create a custom route. Since I don't know the exact controller actions etc that you are using, I will give you a general answer. You can route like (will hit BusinessDatesController's :show_full_date action):
get 'business_dates/:year/:month/:day', to: 'business_dates#show_full_date'
You can link to it like (run rake routes to check correct path):
<%= link_to your_date, full_date_business_dates_path('1985','5','21') %>
The important thing to understand here is that the path helper is in the end just a method that can take arguments. What it can accept is defined in the routes.rb. So, in our case, it will :year, :month and :day parameters.
Once you click this link and hit the :show_full_date action, you can extract the year, month, date using params[:year], params[:month], params[:day] and do with them whatever you need to do. You can similarly define routes for just the year or the month. Hope this helps.
EDIT: You can also give the as: option in the route definition to give a specific name to the path, like as: 'my_funky_name'.
Also, I should add that you should keep such custom routes to a minimum. When it is necessary, then do it. Otherwise stick to the defaults.
I finally figured it out and got it working, so I'll share my answer. big props to arunt for the help! It's a bit messy and probably not the right way I should be doing this, but my first goal was to get it to work and learn how it works along the way. Now I'm looking to tidy up and learn best practices.
Routes
Rails.application.routes.draw do
resources :business_dates, except: :show
get '/business_dates/:year/:month/:day', to: 'business_dates#edit_date', as: 'edit_date'
get '/business_dates/:year/:month', to: 'business_dates#by_days', as: 'by_days'
get '/business_dates/:year', to: 'business_dates#by_months', as: 'by_months'
delete '/business_dates/:year/:month/:day', to: 'business_dates#delete_date', as: 'delete_date'
Controller
class BusinessDatesController < ApplicationController
def index
#business_dates = BusinessDate.all
#years = BusinessDate.pluck(:calendar_date).map{|x| x.year}.uniq
end
def new
#date = BusinessDate.new
end
def by_months
#months = BusinessDate.where("strftime('%Y', calendar_date) = ?", params[:year])
#months = #months.pluck(:calendar_date).map{|x| x.strftime('%m')}.uniq
end
def by_days
#days = BusinessDate.where("cast(strftime('%Y', calendar_date) as int) = ? AND cast(strftime('%m', calendar_date) as int) = ?", params[:year], params[:month])
end
def edit_date
set_business_date
end
def create
#date = BusinessDate.new(date_params)
if #date.valid?
#date.save
redirect_to by_days_path(#date.calendar_date.year, "%02d" % #date.calendar_date.month)
else
flash.now[:alert] = "New business date could not be saved"
render action: "new"
end
end
def update
#set_business_date
#date = BusinessDate.find(params[:id])
#date.update(date_params)
redirect_to by_days_path(#date.calendar_date.year, "%02d" % #date.calendar_date.month)
end
def delete_date
set_business_date
#date.destroy
redirect_to by_days_path(params[:year], params[:month])
end
private
def set_business_date
#date = BusinessDate.where("cast(strftime('%Y', calendar_date) as int) = ? AND cast(strftime('%m', calendar_date) as int) = ? AND cast(strftime('%d', calendar_date) as int) = ?", params[:year], params[:month], params[:day]).first
end
def date_params
params.require(:business_date).permit(:calendar_date, :seasonality)
end
end
index.html.erb
<h1>Business Dates by Year</h1>
<table>
<tr>
<th>Calendar Date</th>
</tr>
<% #years.sort.each do |year| %>
<tr>
<td><%= link_to year, business_date_path(year) %></td>
</tr>
<% end %>
</table>
<br>
<%= link_to 'Create New Business Date', new_business_date_path %>
by_months.html.erb
<h1><%= params[:year] %></h1>
<table>
<tr>
<th>Months</th>
</tr>
<% #months.sort.each do |month| %>
<tr>
<td><%= link_to Date::MONTHNAMES[month.to_i], by_days_path(params[:year], month) %></td>
</tr>
<% end %>
</table>
<br>
<%= link_to 'Create New Business Date', new_business_date_path %>
<br>
<%= link_to 'Back to Years', business_dates_path %>
by_days.html.erb
<h1><%= Date::MONTHNAMES[params[:month].to_i] %>, <%= params[:year] %> <%= params[:id] %></h1>
<table>
<tr>
<th>Date</th>
<th>Seasonality</th>
<th>ID</th>
</tr>
<% #days.sort.each do |day| %>
<tr>
<td><%= day.calendar_date.day %></td>
<td><%= day.seasonality %></td>
<% %>
<td><%= day.id %></td>
<td><%= link_to 'Edit', edit_date_path(day.calendar_date.year, "%02d" % day.calendar_date.month, day.calendar_date.day) %></td>
<td><%= link_to 'Delete', delete_date_path(day.calendar_date.year, "%02d" % day.calendar_date.month, day.calendar_date.day), method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</table>
<br>
<%= link_to 'Create New Business Date', new_business_date_path %>
<br>
<%= link_to 'Back to Months', by_months_path %>
<br>
<%= link_to 'Back to Years', business_dates_path %>
It's all working fine, but I wish I could figure out my :id issue. Not sure how to find a record by :id while using the :year/:month/:day url convention. It's not an issue for this app, considering there should never be more than one of the same date, but it'd be helpful and I'm sure it would cut down on having to search for the record by params[:year],[:month], and [:day]. This thing was a holy terror, but I certainly learned a lot about the differences between arrays, hashes, symbols, attributes, models, methods, and instance variables along the way!

Custom Controller Method, Route, & ERB

I have a search form and I need to be able to filter based on whether or not pets are allowed, but I'm not sure how to accomplish this. I have setup a route, a controller method, and a button but none of that seems to be working.
listings_controller:
def pets_allowed
#listings = #listings.where(pets: true)
end
routes.rb:
get "pets_allowed" => "listings#pets_allowed"
html.erb file:
<div>
<%= link_to 'Pets Allowed', pets_allowed_path, :class => 'button btn-transparent' %>
</div>
Maybe you meant
def pets_allowed
#listings = Listing.where(pets: true)
end
This is a basic example of another way to do what I think you're aiming for (as per comments).
This adds a new action in your Listings controller that returns a filtered list of results based on the users input from the search form on your listings index page. The results are rendered using the same index template. The logic for checking/retrieving results can be modified based on what you want. If you just want a check box, only have a checkbox or a button that calls the action.
You could do similar logic but use ajax to return the results and render them on the index template using a partial.
This should give you enough information to google for examples/tutorials and try different ways of getting what you want.
Add a route:
# routes.rb
get 'pets_allowed', to: 'things#pets_allowed'
Add a new action:
# listings_controller.rb
# GET /things
# GET /things.json
def index
#listings = Listing.all
end
# Get /pets_allowed
def pets_allowed
#listings = Listing.where("name LIKE ? and pets = ?", "%#{params[:name]}%", params[:pets] )
render template: "listings/index", variable: #listings
end
Add a search form to your view:
# listings/index.html.erb
<h1>Listings</h1>
<%= form_tag('pets_allowed', method: 'GET' ) do %>
<%= label_tag :name %><br>
<%= text_field_tag :name %>
<br>
<%= label_tag :pets %><br>
<%= check_box_tag :pets, 't' %>
<br>
<%= submit_tag("Search") %>
<% end %>
<table>
<thead>
<tr>
<th>Listing name</th>
</tr>
</thead>
<tbody>
<% #listings.each do |listing| %>
<tr>
<td><%= listing.name %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<%= link_to 'New Listing', new_listing_path %>

Rails getting the error: First argument in form cannot contain nil or be empty

I've been getting this error for a couple days and I'm totally blocked. I tried redoing the model (I'm following the rails starting guide) and just not getting anywhere.
First argument in form cannot contain nil or be empty
I have time_delta as a nested class of stock im trying to create a form to view and create new time_deltas on a stock's show and I keep getting the above error.
Heres my time_delta controller:
class TimeDeltasController < ApplicationController
def new
#stock = Stock.find(params[:stock_id])
#time_delta = #stock.time_deltas.build
respond_with(#time_delta)
end
def create
#stock = Stock.find(params[:stock_id])
#time_delta = #stock.time_deltas.build(params[:stock])
#time_delta.save
end
end
Heres my view for the specific stock
<h1> Stock </h1>
<table>
<tr>
<th>Stock</th>
<th>Hashtag</th>
</tr>
<tr>
<td><%= #stock.name %></td>
<td><%= #stock.hashtag %></td>
</tr>
</table>
<h2>Deltas: </h2>
<table>
<tr>
<th>Stock</th>
<th>Hashtag</th>
</tr>
<% #stock.deltas.each do |delta| %>
<tr>
<td><%= #delta.start %></td>
<td><%= #delta.length %></td>
</tr>
<% end %>
</table>
<h2>Add a TimeDelta:</h2>
<%= form_for([#stock,#time_delta]) do |f| %>
<p>
<%= f.label :start %><br>
<%= f.text_field :start %>
</p>
<p>
<%= f.label :length %><br>
<%= f.text_area :length %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
<%= link_to 'Back', stocks_path%>
<%= link_to 'Edit', edit_stock_path(#stock)%>
Heres how I added the route in my routes.rb:
resources :stocks do
resources :time_deltas
end
Any information would be greatly appreciated, I'm really stuck.
EDIT: Stocks controller stuff
def show
#time_delta = #stock.time_deltas.build
#stock = find_stock
end
private
def find_stock
return Stock.find(params[:id])
end
You are trying to access #time_delta variable in your stocks/show view, but it is not set. Add the following line to StocksController#show action.
#time_delta = #stock.time_deltas.build
EDIT:
Also there is a problem with the naming of your TimeDelta model, because in Ruby 'delta' is plural of 'deltum'. To adhere to Rails conventions, change the the model name to TimeDeltum or alternatively tell Rails to use 'deltas' as the plural form of 'delta'. You can learn how to do it here.
The error basically means you've not set the variables for use in form_for
As you're a beginner, let me explain how it works:
form_for is basically an instance method (helper) which takes ActiveRecord objects, and outputs them into an HTML form. Your definitions of elements (f.____field) are for the method to determine which HTML to output
Like all methods, form_for has arguments/parameters which it relies on to help the method run correctly. The parameters for form_for include a correctly formatted ActiveRecord object, which is why you have to call Model.new each time you want to use it
Your error means the method cannot process the variables (objects) you've sent, either from lack of data (empty) or non-existence (nil). To fix this, as #vee has mentioned, you need to make sure your form_for is receiving the correct data
I would try this:
class TimeDeltasController < ApplicationController
def new
#stock = Stock.find(params[:stock_id])
#time_delta = #stock.time_deltas.build
respond_with(#stock, #time_delta)
end
def create
#stock = Stock.find(params[:stock_id])
#time_delta = #stock.time_deltas.build(params[:stock])
#time_delta.save
end
end
You should read up on respond_with to see how it works

Ruby on Rails - How to combine display Search results in the View

I have Search controller that searches 2 models - Posts and Categories. The search works, however I am unable to display results in the View correctly - I can't get category names to show up.
I am very confused and frustrated at this point and hope to find some help!
I'm pretty sure (99% sure) the problem is in the View somewhere, because I can get results to display through render inspect thingy.
SearchController.rb
class SearchController < ApplicationController
def index
#posts = Post.search(params[:search])
#categories = Category.search(params[:search])
# combine the results
#results = #posts + #categories
#results.uniq # may be necessary to remove duplicates
end
end
index.html.erb (views/search)
<%= render 'posts/posts', :posts => #posts %>
_posts.html.erb (view/posts)
<h1>Listing posts</h1>
<table>
<tr>
<th>Name</th>
<th>Category</th>
<th>Description</th>
<th>Flag</th>
</tr>
<% if posts %>
<% posts.each do |post| %>
<tr>
<td><%= post.name %></td>
<td><%= post.category.name %></td>
<td><%= post.description %></td>
<td><%= post.flag %></td>
</tr>
<% end %>
<% else %>
<tr><td>No posts</td></tr>
<% end %>
</table>
I can get posts that match the search to display, but I can't display categories. How can I do this? Any help highly appreciated!! Thank you.
If you are using a search backend like sunspot solr then you would be able to combine the searches like
#search = Sunspot.search [User, Company] do
fulltext params[:search]
end
#results = #search.results
And then return the necessary values. In this example, it's showing where you can retrieve the class of the action (controller_name) may not work depending on which controller the results are returned in.
<% #results.each do |result| %>
<% case result.class.to_s %>
<% when "Company" %>
<li><%= "Company: #{result.name}" %></li>
<% when "User" %>
<li><%= "User: #{result.username}" %></li>
<% end %>
<% end %>
The answer (I include all the things I changed to make it work + few files that I didn't change but that have to be there), or How to make simple search for multiple models:
SearchController.rb
class SearchController < ApplicationController
def index
#posts = Post.search(params[:search])
end
end
post.rb
class Post < ActiveRecord::Base
attr_accessible :category_id :name :description :flag
belongs_to :category
def self.search(search)
if search
find(:all, :joins => :category, :conditions => ['posts.equipment LIKE ? OR posts.description LIKE ? or categories.name like ?', "%#{search}%", "%#{search}%", "%#{search}%"])
else
find(:all)
end
end
search/index.html.erb
<%= render 'posts/posts', :posts => #posts %>
I added 2 files _post.html.erb and _category.html.erb. They are similar, this is _post.html.erb:
post: <%= post.name %>
(This might not be necessary in some cases or for some models. I can search a third model without this file in its' views. However the third model doesn't have file like _posts.html.erb either).
Finally, _posts.html.erb remains the same:
...
<% if posts %>
<% posts.each do |post| %>
<tr>
<td><%= post.name %></td>
<td><%= post.category.name %></td>
<td><%= post.description %></td>
<td><%= post.flag %></td>
</tr>
<% end %>
<% else %>
<tr><td>No posts</td></tr>
<% end %>
...
This works now. Can add new models to the search easily. The only other thing needed for the search is input field.

Nested forms in rails for existing objects

In my app, I have a page where I want admin users to be able to update a particular characteristic of my "Package" model, which belongs to both the "Order" model and the "Item" model. It's a little complicated, but I basically want to present in a table all of the Packages belonging to a given Item, ordered in a particular way (that's what my packages_for_log method below does), with two blanks for updating the weight of the item. All the updates should ideally be submitted at once, with a single submit button at the bottom of the page. I've attempted a whole bunch of solutions, and my current one is below, but gives this error when I visit the page in my server:
undefined method `actual_lbs' for #<ActionView::Helpers::FormBuilder:0x007ff67df6c9c8>
The error's confusing to me, cause I was hoping that I was calling that method on the package instance, not a helper. Bit confused. At any rate, my code is below. The relevant section of the view:
<% form_for(#item) do |a| %>
<% #item.packages_for_log.each do |p| %>
<%= a.fields_for p do |i| %>
<tr>
<td><%= p.order.name %></td>
<td><%= p.order.processed_notes %></td>
<% if p.order.user %>
<td><%= "#{p.order.user.name.first(3).upcase}-#{p.id}" %></td>
<% else %>
<td><%= p.order.id %></td>
<% end %>
<td>
<%= i.text_field :actual_lbs %>
</td>
<td>
<%= i.text_field :actual_oz %>
</td>
<%= i.hidden_field :true_weight, value: (i.actual_lbs + i.actual_oz/16) %>
</tr>
<% end %>
<% end %>
<% end %>
Relevant section of the package.rb model file:
attr_accessible :order_id, :price, :true_weight, :actual_lbs, :actual_oz
attr_accessor :actual_lbs, :actual_oz # These two are virtual attributes for the above calc
And I added resources :packages to my routes file.
Any idea what I'm doing wrong? It's important to me that I loop through to create a table based on "p" and then edit that same "p" object. Just not sure how to do it. Pretty new to Rails.
I think your problem is this line:
<%= i.hidden_field :true_weight, value: (i.actual_lbs + i.actual_oz/16)
You need to put p.actual_lbs and p.actual_oz
EDIT: By the way, you probably need to move the true weight calculation to your controller action (CREATE action). I don't think :true_weight will get passed as you intended it to, using the above method.

Resources