The Rails way to creating in-app links to non-trivial routes? - ruby-on-rails

I've been digging through routing documentation and seem to have only uncovered half of the necessary info needed for this one.
If I create a route which looks like:
match 'attendances/new/:class_date/:student_id'
I'm afraid I'm completely unclear on how to create an appropriate link_to incantation which is capable of fulfilling the above.
For example, I seem to have no problems creating this URL:
http://localhost:3000/attendances/new?class_date=2012-05-07&student_id=5
but I've yet to find appropriate docs which explains how to create this:
http://localhost:3000/attendances/new/2012-05-07/5
Could someone provide a helpful example and/or a link to docs which discusses how to do this?
I realize that trying to use link_to might be completely inappropriate here. And I realize I could munge together some code which will craft the appropriate links, but I suspect that doing so will completely miss some better, Ruby-on-Rails way to doing this.
Edit: Corrected proposed match route, above.
Edit 2: Going on "mu is too short"'s suggestion, here's what my routes.rb now looks like:
NTA::Application.routes.draw do
resources :students
resources :libraries
resources :year_end_reviews
resources :notes
resources :ranktests
resources :attendances
match 'attendances/new/:class_date/:student_id', :as => :add_attendance
resources :ranks
get "home/index"
root :to => "home#index"
end
and here's the relevant view:
<% today = Date.today %>
<% first_of_month = today.beginning_of_month %>
<% last_of_month = today.end_of_month %>
<% date_a = first_of_month.step(last_of_month, 1).to_a %>
<h2><%= today.strftime("%B %Y") %></h2>
<table id="fixedcolDT">
<thead>
<tr>
<th>Name</th>
<% date_a.each do |d| %>
<th><%= d.day %></th>
<% end %>
</tr>
</thead>
<tbody>
<% #students.each do |s| %>
<tr>
<td><%= s.revfullname %></td>
<% date_a.each do |d| %>
<% student_attend_date = Attendance.find_by_student_id_and_class_date(s.id, d) %>
<% if student_attend_date.nil? %>
<td><%= link_to "--", add_attendance_path(d, s.id) %></td>
<% else %>
<td><%= student_attend_date.class_hours %></td>
<% end %>
<% end %>
</tr>
<% end %>
</tbody>
</table>
and here's what I get back after the initial reload (before trying to restart WEBrick):
ArgumentError
missing :controller
Rails.root: /Users/jim/Documents/rails/NTA.new
Application Trace | Framework Trace | Full Trace
config/routes.rb:15:in `block in <top (required)>'
config/routes.rb:1:in `<top (required)>'
This error occurred while loading the following files:
/Users/jim/Documents/rails/NTA.new/config/routes.rb
I'll pastebin what I got back after my failed attempt to restart WEBrick, if interested.

First of all, you want to give the route a name so that you'll get the appropriate helper methods:
match ':attendances/:new/:class_date/:student_id' => 'controller#method', :as => :route_name
That will generate two methods that you can use to build the URL:
route_name_path: The path for the URL, no scheme, hostname, ...
route_name_url: The full URL including scheme, hostname, ...
Those methods will use their parameters for the route's parameter values in order so you could say:
<%= link_to 'Pancakes!', route_name_path(att, status, date, id) %>
and :attendances would be att, :new would be status, etc. Alternatively, you could pass a Hash to the method and use the parameter names directly:
<%= link_to 'Pancakes!', route_name_url(
:attendances => att,
:new => status,
:class_date => date,
:student_id => id
) %>

Related

UrlGenerationError in Rails App

I've searched this site and asked a couple other programmers and can't figure out what the problem is with my code. I'm a beginner to Rails, so I'm still trying to figure everything out.
I'm getting this error:
ActionController::UrlGenerationError in Lists#show
No route matches {:action=>"toggle_completed", :controller=>"tasks", :id=>nil, :list_id=>3} missing required keys: [:id]
Here is the code it's referring to on the Lists#show page:
<p id="notice"><%= notice %></p>
<h3>add a task</h3>
 <%= render 'tasks/form', task: Task.new() %>
<p>
<strong>List Name:</strong>
<%= #list.name %><p></p>
<% #list.tasks.each do |task| %>
<td><%= task.id %></td>
<td><%= task.name %></td>
<td><%= task.completed %></td>
<td><%= link_to('Toggle', toggle_completed_list_task_path(:list_id => #list.id,
:id => task.id), :method => :put ) %></td><br />
<% end %>
</p>
My Routes:
resources :lists do
resources :tasks do
member do
put :toggle_completed
end
end
end
Tasks Controller:
def toggle_completed
#task = Task.find(params[:id])
#task.completed = !#task.completed
#task.save
end
List has_many: tasks
and
Task belongs_to: list
I've experimented a bit and on my Lists#show page, if I add this line:
<%= task.id %>
The correct value appears on the page, so I'm not sure why it's coming up nil. I've searched the site and haven't found anything that really discusses this exact issue. Thanks!
create a separate routes for toggle_completed ex-
put 'list/task/:id', to: 'tasks#toggle_completed', as: :list_task_completed
and
<%= link_to('Toggle', list_task_completed_path(:id => task.id), :method => :put ) %>

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!

Expand search functionality

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)

Undefined method `time_series_path' error

I have this error when loading index.html.rb.
undefined method `time_series_path' for #<#<Class:0x007f6aac0d2a28>:0x007f6aac0d1358>
In routes.rb I have
namespace :viewer do
resources :time_series
end
In the TimeSeriesController I have
class Viewer::TimeSeriesController < ApplicationController
def show
#time_series = TimeSeries.find(params[:id])
end
def index
#time_series = TimeSeries.all.paginate(:page => params[:page], :per_page => 20)
end
end
In the index.html.rb I have
<h1>Listing of time series</h1>
<table >
<tr>
<th>Kind</th>
<th></th>
</tr>
<% #time_series.each do |t| %>
<tr>
<td><%= t.kind %></td>
<td><%= link_to 'Show', t %></td>
</tr>
<% end %>
</table>
<%= will_paginate #time_series %>
The error occurs for the link_to 'Show' line.
Any ideas on how to resolve this? Thanks.
If you use a namespace, the namespace becomes part of the route name.
viewer_time_series_path
not
time_series_path
To double check, you can print out the list of all the routes
rake routes
and grep to immediately check the name
rake routes | grep time_series
In your code, you either pass the namespace as array in the link_to helper along with the object
<%= link_to 'Show', [:viewer, t] %>
or (I prefer this solution) you write the corresponding path explicitly.

Editing multiple nested records simultaneously in Rails

I'm creating a simple quiz application where a question can have multiple answers. To improve the usability of my app, I want users to be able to edit ALL the answers for a given question in the same form:
I found this great Railscast/Asciicast episode which does almost EXACTLY what I want to do
http://asciicasts.com/episodes/198-edit-multiple-individually
However, there's a catch. My answers model is nested within questions like so:
map.resources :answers, :has_one => :question
map.resources :questions, :has_many => :answers
So when it comes time to define the route and form tag I come a bit unstuck... The tutorial suggests creating 2 new controller methods and defining the routes and form tags as follows:
map.resources :products, :collection => { :edit_individual => :post, :update_individual => :put }
<% form_tag edit_individual_products_path do %>
But this doesn't work in my case as Answers are dependent on Questions... Any ideas on how to translate this tutorial for my nested models?
Working with nested routes looks pretty from some point of view, but always become a bit tricky. In order for this to work you will have to
Fix the routes definition
Adapt the URL generators in all views (like form_for or answers_path)
Routes
First things first: Specifying associations within the routes won't allow you to add custom routes to the second class. I would do something like this:
map.resources :questions do |question|
question.resources :answers, :collection => {
:edit_individual => :post,
:update_individual => :put }
end
Views
It's really important to notice the change in URL generators:
edit_answer_path(#answer) => edit_question_answer_path(#question, #answer)
edit_individual_answer_path(#answer) => edit_individual_question_answer_path(#question, #answer)
I've made a fast adaptation of the Railscasts views:
<!-- views/answers/index.html.erb -->
<% form_tag edit_individual_question_answer_path(#question) do %>
<table>
<tr>
<th></th>
<th>Name</th>
<th>Value</th>
</tr>
<% for answer in #answers %>
<tr>
<td><%= check_box_tag "answer_id_ids[]", answer.id %></td>
<td><%=h answer.name %></td>
<td><%=h answer.value %></td>
<td><%= link_to "Edit", edit_question_answer_path(#question, answer) %></td>
<td><%= link_to "Destroy", question_answer_path(#question, answer), :confirm => 'Are you sure?', :method => :delete %></td>
</tr>
<% end %>
</table>
<p>
<%= select_tag :field, options_for_select([["All Fields", ""], ["Name", "name"], ["Value", "value"]]) %>
<%= submit_tag "Edit Checked" %>
</p>
<% end %>
<!-- views/answers/edit_individual.html.erb -->
<% form_tag update_individual_question_answers_path, :method => :put do %>
<% for answer in #answers %>
<% fields_for "answers[]", answer do |f| %>
<h2><%=h answer.name %></h2>
<%= render "fields", :f => f %>
<% end %>
<% end %>
<p><%= submit_tag "Submit" %></p>
<% end %>
Extra
As you may have seen, you will require the variable #question within your views, so I would recommend you to have a before_filter in your AnswersController that fetches the question object:
AnswersController
before_filer :get_question
[...]
private
def get_question
# #question will be required by all views
#question = Question.find(params[:question_id])
end
end
Enjoy your nested routes!!

Resources