How to abstract link_tos for nested resources on multiple models - ruby-on-rails

I have a nested resource in a Rails 3.2 app.
resources :users do
resources :posts
end
resources locations do
resources :posts
end
resources categories
resources :posts
end
In a shared sidebar module displayed on User, Location and Category show pages I have a link to the nested resource
<% if controller.class == UsersController %>
<%= link_to "Posts", user_posts_path(#user) %>
<% elsif controller.class == LocationsController %>
<%= link_to "Posts", location_posts_path(#location) %>
<% elsif controller.class == CategoriesController %>
<%= link_to "Posts", category_posts_path(#category) %>
<% end %>
Is there a clever way to DRY this up? I'm thinking something like <%= link_to "Posts", controller_name_posts_path(#current_object) %>

It's not as clever as that but I'd simplify as a first step by defining #resource_path (actually I'd probably expose a method) in each controller so that the view template is simplified to something like
<%= link_to "Posts", #resource_path %>
In the controller action - something like:
#resource_path = user_posts_path(#user)
or similar

Using content_for(or just provide) should do the trick as well.
Without touching your controller add call to content_for at the top of your view show files:
example for users/show:
<% content_for :resource_path, user_posts_path(#user) %>
In your shared sidebar module change your block of code to:
<%= link_to "Posts", yield :resource_path %>

Related

Link_to to update attribute in table

I want a link to update a resource, without using an HTML form.
I have a Kanban board listing projects grouped by their status. Via the click of a button I want to update the status of a project moving them to the next list. This should happen on the workflows index: /tenants/{id}/workflows
Problem
The button_to or link_to seems to be feasible avoiding a form, but I don't know which route and params to use in the index.html.erb. Any advice would be much appreciated.
Routes
resources :tenants do
resources :members
resources :projects
resources :workflows
end
Workflows/index.html.erb
<%- model_class = Project -%>
<% #projects.where(tenant_id: #tenant.id, :status => "Nominated").each do |project| %>
<%= project.title %>
<%= button_to "Update", XXXX, method: :put, params: { status: "In Progress" } %>
<% end %>
<% end %>

Rails route to a resource

I'm building a Rails app that is a podcast directory. I have Podcasts and Episodes. Episode belongs to Podcast and Podcast has many Episodes. On the home page I want to show the last 5 episodes that have been created and link to them.
I have it working with this, though this is obviously not the way to do it:
<% #episodes.each do |episode| %>
<%# link_to episode do %>
<%= episode.title %>
<%# end %>
<% end %>
The link_to is commented out because that's part of my problem.
Here is the index controller:
def index
#podcasts = Podcast.where.not(thumbnail_file_name: nil).reverse.last(5)
#episodes = Episode.where.not(episode_thumbnail_file_name: nil).reverse.last(5)
end
Here is the routes file:
Rails.application.routes.draw do
devise_for :podcasts
resources :podcasts, only: [:index, :show] do
resources :episodes
end
authenticated :podcast do
root 'podcasts#dashboard', as: "authenticated_root"
end
root 'welcome#index'
end
Results of rake routes | grep episode:
podcast_episodes GET /podcasts/:podcast_id/episodes(.:format) episodes#index
POST /podcasts/:podcast_id/episodes(.:format) episodes#create
new_podcast_episode GET /podcasts/:podcast_id/episodes/new(.:format) episodes#new
edit_podcast_episode GET /podcasts/:podcast_id/episodes/:id/edit(.:format) episodes#edit
podcast_episode GET /podcasts/:podcast_id/episodes/:id(.:format) episodes#show
PATCH /podcasts/:podcast_id/episodes/:id(.:format) episodes#update
PUT /podcasts/:podcast_id/episodes/:id(.:format) episodes#update
DELETE /podcasts/:podcast_id/episodes/:id(.:format) episodes#destroy
How can I correctly create a text link of the title using a link_to that links directly to the episode? Thanks!
When you're using link_to with a block, the only thing you need to pass into the block is the text of the link, so you should be able to do this (assuming your routes are setup properly):
<% #episodes.each do |episode| %>
<%= link_to episode, class="tt-case-title c-h5" do %>
<%= episode.title %>
<% end %>
<% end %>
UPDATE
You really don't even need to use a block here. This should work fine for you and be a little more succinct.
<% #episodes.each do |episode| %>
<%= link_to episode.title, episode, class="tt-case-title c-h5" %>
<% end %>
UPDATE #2
Thanks for including your route info. Try this:
<% #episodes.each do |episode| %>
<%= link_to episode.title, podcast_episode_path(episode.podcast, episode), class="tt-case-title c-h5" %>
<% end %>

Trying to filter results by a string attribute in rails

I am relatively new to Rails, I am trying to create a cookbook app/site from scratch, I'm not following any tutorials or anything like that. Anyway... I am still in the early stages of the site but I am now to the part where I am wanting to display a index list of all the different kinds of recipes. But I want to filter the list, for example:
If I click on the 'vegetables' button on the navigation bar I would like to be take to an index page that only displays the different vegetable recipes.
I've gone ahead and added a string attribute to Recipes, called "category" thus I will be able to differentiate between Meat, Seafood, Poultry, Appetizers and Vegetable recipes. My goal is to only need the one controller 'recipes' and in the index action be able to conditionally filter on a param. Thus filtering the list by 'category' of food. But I am unsure how to go about doing so.
Here is my RecipesController:
class RecipesController < ApplicationController
def index
#recipes = Recipe.all
end
def show
#recipe = Recipe.find(params[:id])
end
def new
end
def edit
end
end
Here is my Routes file:
Rails.application.routes.draw do
resources :recipes
get 'vegetables' => 'recipes#vegetables'
get 'poultry' => 'recipes#poultry'
get 'meat' => 'recipes#meat'
get 'seafood' => 'recipes#seafood'
get 'appetizers' => 'recipes#appetizers'
devise_for :users
get 'about' => 'welcome#about'
root to: 'welcome#index'
end
Here is the application layout file that contains the navigation bar:
<!DOCTYPE html>
<html>
<head>
<title>Mrs. P's Cookbook</title>
<link href='https://fonts.googleapis.com/css?family=Mate+SC' rel='stylesheet' type='text/css'>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
<%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
<%= csrf_meta_tags %>
</head>
<body>
<div class="top-banner">
<h1 class="banner-logo"> <%= link_to "Mrs. P's Cookbook", root_path %></h1>
<nav>
<%= link_to "POULTRY", poultry_path, class: "nav-link" %> |
<%= link_to "MEAT", meat_path, class: "nav-link" %> |
<%= link_to "SEAFOOD", seafood_path, class: "nav-link" %> |
<%= link_to "APPETIZERS", appetizers_path, class: "nav-link" %> |
<%= link_to "VEGETABLES", vegetables_path, class: "nav-link" %> |
<%= link_to "ABOUT", about_path, class: "nav-link" %>
</nav>
<% if current_user %>
Hello <%= current_user.email %>! <%= link_to "Sign out", destroy_user_session_path, method: :delete %>
<% else %>
<%= link_to "Sign In", new_user_session_path %> or
<%= link_to "Sign Up", new_user_registration_path %>
<% end %>
</div>
<div class="white-field">
<%= yield %>
</div>
</body>
</html>
Here is my Recipe.rb model file:
class Recipe < ActiveRecord::Base
has_many :comments
end
Here is my Recipe table:
create_table "recipes", force: :cascade do |t|
t.string "title"
t.text "body"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "category"
end
I've got the different category views, Vegetables, Meat, Poultry, Seafood and Appetizers all inside the Recipe view folder. All of the views are empty except for some text that just says "Vegetable recipes will be listed here.", "Meat recipes will be listed here.", "Seafood recipes will be listed here.", etc..
I know what I am asking might be a tall order, so any help you guys can provide I will be extremely grateful for. If you need anymore information please let me know.
I came up with two possible solutions (among many! :) )
Solution 1
One solution could be using a query string. The routes file would be
Rails.application.routes.draw do
resources :recipes
devise_for :users
get 'about' => 'welcome#about'
root to: 'welcome#index'
end
Now create the links with the category as argument. In your view:
...
<nav>
['poultry','meat','seafood','appetizers','vegetables'].each do |category|
<%= link_to category.upcase,
recipes_path(category: category),
class: "nav-link" %> |
end
<%= link_to "ABOUT", about_path, class: "nav-link" %>
</nav>
...
And in the controller you can capture the category, querying the model as already showed in the other answers. For example
class RecipesController < ApplicationController
def index
#recipes = params[:category].blank? : Recipe.all :
Recipe.where(category: params[:category])
end
...
end
But isn't that elegant if you want to customize the view of a single category.
Solution 2
Add the following route, (before the resources :recipes )
Rails.application.routes.draw do
get 'recipes/:category' => 'recipes#category', as: :recipes_category
resources :recipes
...
end
Note that with the as: :recipes_category you can reference to this path using the recipes_category_path helper
Now we need to add the category action to the RecipesController
class RecipesController < ApplicationController
...
def category
#recipes = Recipe.where(category: params[:category])
end
...
end
And the the path in the link_to will change to
...
<%= link_to category.upcase,
recipes_category_path(category: category),
class: "nav-link" %> |
...
IMHO that's cleaner if you want to customize your view without caring the presence of the param and the url looks prettier :).
Looking forward
As already suggested by #denys281, the category field smells like a separate entity, related with the recipes with a N:1 relation.
The two model will have the associations
class Recipe < ActiveRecord::Base
belongs_to :category
end
class Category < ActiveRecord::Base
has_many :recipes
end
In that way, for example, you can easily have all the categories by calling
Category.all
or the recipes for a category with
#category = Category.find(2)
#recipes = #category.recipes
It really depends on what you want to achieve. I hope I gave you some ideas on the argument.
Regarding the other suggestion of using Slim, I disagree. It's just syntactic sugar, it's not standard and it will confuse you: the most of the code you will find around will have the standard syntax.
Get your hands dirty before using some other tools, it will give you a better idea of the mechanisms behind, a better understanding if the tool will solve your needs and how you can customize it.
I suggest you to read the official rails guide, the countless blogs and articles and try to read code written by others ( thanks Open Source! ).
Use a named scope like this
class Recipe < ActiveRecord::Base
scope :by_category, lambda {|cat|
where(category: cat)
}
...
end
Then you can call
Recipe.by_category("Vegetables")
You can also do:
scope :vegetables, lambda {
where(category: "Vegetables")
}
and then call
Recipe.vegetables
I think you need use some tutorial, in this way you will see best practices and so on. Also I think you should read some good book about relational databases.
Use slim ;
Create Category table. Recipe belongs_to :category. Category has_many :recipes. Check association basics ;
How to get recipes you can look at Active Record Query Interface (Something like Recipe.where(category_id: params[:id])) ;
Also check about partials
Your routes should coincide with a function in your controller so when you have a route to 'recipes#meat', your controller would be looking for a 'meat' function and a 'meat' view
I would remove those routes and have one called
'get recipe/:recipe'
and pass that parameter(the string) to the show function.
This way you could access a specific recipe like so:
#recipes = Recipe.where("category = ?", params[:recipe])

link_to with Namespaces in Rails

I'm trying to get an admin interface working in Rails, but I'm having trouble using link_to with the nested routes. I'm trying to get a link to /admin/cake_class/:id but it's sending me to admin/cake_class.:id instead.
config/routes.rb:
namespace :admin do
resources :cake_class
end
/app/views/admin/cake_class/index.html.erb
<h1>all classes</h1>
<% #classes.each do |c| %>
<%= c.date %>
<%= c.name %>
<%= link_to 'Show', admin_cake_class_path(c) %>
<% end %>
Any suggestions?
The :resources should be plural, :cake_classes, which could be tripping you up.
What does the output of rake routes show?

Give, Nested Resourced, Books > Authors… How to Link to the Author SHOW page

I have the following rails 3 nested models:
resources :books do
resources :authors
end
I now have a view here: /books/131/authors/
And I want to each record to link to something like: /books/131/authors/3333
<% #authors.each do |author| %>
<%= link_to 'author.name', book_author_path(#book, #author) %>
<% end %>
but that error's with: No route matches {:action=>"destroy", :controller=>"authors"}
I also tried:
<%= link_to 'author.name', [#book, author] %>
Problem is the code keeps linking to /authors/3333, not /books/131/authors/3333
Ideas? thanks!
book needs to be defined in the author controller for def index
<%= link_to "title", book_author_path(#book, author) %>

Resources