Currently, I'm building a blog in Rails and I am being curious is there a right way to display resources in the following manner?
In this case you may be able to list all the posts, and if needed, separate category posts.
You'd normally say - use scopes, however I'm not sure scopes are gonna produce the following adressess: /blog/features, /blog/releases.
So, how can I do this?
#config/routes.rb
resources :blogs, path: "blog" do
get ":category", to: :index, on: :collection #-> url.com/blog/:category
end
#app/controllers/blogs_controller.rb
class BlogsController < ApplicationController
def index
#posts = params[:category] ? Post.joins(:category).where(category: {name: params[:category]}) : Post.all
end
end
#app/views/posts/index.html.erb
<% #posts.each do |post| %>
...
<% end %>
Related
I'm new to rails, and I've already learnt how to do CRUD using scaffold and using resource, I wanna know how to Do CRUD without using resource, However what I want to do is create custom methods for CRUD in the controller that will be like the traditional CRUD model. Please help me with this.
Actually, for the action index / new / show / create / edit / update / destroy, this is a convention in Ruby On Rails
If I'm right and if you're trying to change their name using resources in routes.rb (Changing by exemple the new action into def my_restaurant), Rails will render
Unknown action The action 'new' could not be found for
RestaurantsController
Netherless, you can create some methods to perform some particular action into the index, and add them in the "private section" you want to hide them to the public. There is no routes attach to this method.
class RestaurantsController < ApplicationController
def index
#restautants = Restaurant.all
#restaurants.sort_by_name
end
private
def sort_by_name
#some action here
end
end
If you want to create your own name method, you can personnalise it but you can't use resources "shortcut" in the routes.rb
by exemple :
#reviews_controller.rb
class ReviewsController < AplicationController
def index
#reviews = Reviews.all
end
def update
#review = Review.find(review_params[:id])
end
def history
#some action
end
private
def review_params
params.require(:review).permit(:liked, :comment, :id)
end
end
Then add a view
#app/views/reviews/history.html.erb
Don't forget the routes :
Rails.application.routes.draw do
resources :reviews, only: [:index, :update] do
collection do
get :history
end
end
end
I hope this will help you and complete the previous answer.
as for your second question :
I still do have one doubt tho..... Will <%= form_for #post do |f| %>
this form be enough for new_post and edit_post, will it automatically
identify them? If that's not enough can you please tell me the what
comes at new_post.html.erb and edit_post.html.erb....Thanks again for
the help.
If the form is the same for your new_post and edit_post, i may suggest you to put into a partial file. (For this example I used simple_form gem)
#app/views/posts/_form.html.erb
<%= simple_form_for(#post) do |f| %>
<%= f.input :name, label: "post name" %>
<%= f.input :photo, as: :file %>
<%= f.submit "Save", class:"btn btn-small btn-success" %>
<% end %>
and then render the partial in your views new file.
#app/views/posts/new.html.erb
<div>
<h1>New Post</h1>
</div>
<%= render "form" %>
Well I hope I could help you with this answer.
Do not hesitate too to read ruby documention. You may find more information that you're looking for too.
My answer may be redundant but it's the better way for me to clearly explain it...
In oder to use your own custom methods you need to create them in your controller, setup the route and if needed create an view.
# PostsController
def create_post
# Do your create stuff here
end
def read_post
# Do your read stuff here
end
def update_post
# Do your update stuff here
end
def delete_post
# Do your delete stuff here
end
# routes
post '/create_post', to: 'posts#create_post'
get '/read_post/:id', to: 'posts#read_post'
put '/update_post/:id', to: 'posts#update_post'
delete 'delete_post/:id', to: 'posts#delete_post'
With the controller and routes setup you will only need a view for the read_post method. The best way to do that is create the file: views/posts/read_post.html.erb
There is 7 CRUD routes to Create, Read, Update and Delete.
Rails.application.routes.draw do
get "restaurants", to: "restaurants#index"
get "restaurants/new", to: "restaurants#new", as: :new_restaurant
post "restaurants", to: "restaurants#create"
# NB: The `show` route needs to be *after* `new` route.
get "restaurants/:id", to: "restaurants#show", as: :restaurant
get "restaurants/:id/edit", to: "restaurants#edit", as: :edit_restaurant
patch "restaurants/:id", to: "restaurants#update"
delete "restaurants/:id", to: "restaurants#destroy"
end
So once the route create, you can create in the controller, the action that you need
class RestaurantsController < ApplicationController
def index
#restaurants = Restaurant.all
end
end
and the view
app/views/restaurants/index.html.erb.
The best practice is to create only the routes that you will need and to work in silos.
1 route, 1 controller action, 1 view.
Hi so I'm really new to rails and I am trying to figure out how to use this gem https://github.com/jonhue/acts_as_favoritor.
At the moment I am trying to get a student to favourite an internship.
My favourites controller looks like this
class FavouritesController < ApplicationController
def new
#internship = Internship.find(params[:id])
current_student.favourite(internship)
redirect_to_show
end
def show
current_students.all_favourites
end
end
My routes look like this
Rails.application.routes.draw do
resources :favourites
devise_for :students
resources :internships
devise_for :companies
# For details on the DSL ava
And my button to add an internship shown in the index is this
<td><%= link_to 'favourite', new_favourite_path %></td>
Using the gem I have put in the models acts_as_favoritor in the student model and acts_as_favoritable in the internship model. I have been banging my head against the wall for ages trying to understand how to write methods from so if someone could please help me out, Thank you heaps!
I think if I were you, I would make my routes something like:
resources :internships do
member do
post :favorite
post :unfavorite
end
end
Which will give you among other things:
favorite_internship POST /internships/:id/favorite(.:format) internships#favorite
unfavorite_internship POST /internships/:id/unfavorite(.:format) internships#unfavorite
internships GET /internships(.:format) internships#index
POST /internships(.:format) internships#create
new_internship GET /internships/new(.:format) internships#new
edit_internship GET /internships/:id/edit(.:format) internships#edit
internship GET /internships/:id(.:format) internships#show
PATCH /internships/:id(.:format) internships#update
PUT /internships/:id(.:format) internships#update
DELETE /internships/:id(.:format) internships#destroy
Then in your view, you would do something along the lines of:
<td>
<% if current_student.favorited?(internship) %>
<%= link_to 'unfavourite', unfavorite_internship_path(internship), method: :post %>
<% else %>
<%= link_to 'favourite', favorite_internship_path(internship), method: :post %>
<% end %>
</td>
This, naturally, assumes you have access to current_student and internship in your view.
Then, in your InternshipsController, you would do something like:
class InternshipsController < ApplicationController
def favorite
#internship = Internship.find(params[:id])
current_student.favorite(#internship)
# redirect somewhere
end
def unfavorite
#internship = Internship.find(params[:id])
current_students.unfavorite(#internship)
# redirect somewhere
end
end
Now, favorite and unfavorite are not very restful. So, I guess you could do:
resources :internships do
scope module: :internships do
resources :favorites, only: [:create] do
collection do
delete '/', action: :destroy
end
end
end
end
Which would give you:
internship_favorites DELETE /internships/:internship_id/favorites(.:format) internships/favorites#destroy
POST /internships/:internship_id/favorites(.:format) internships/favorites#create
internships GET /internships(.:format) internships#index
POST /internships(.:format) internships#create
new_internship GET /internships/new(.:format) internships#new
edit_internship GET /internships/:id/edit(.:format) internships#edit
internship GET /internships/:id(.:format) internships#show
PATCH /internships/:id(.:format) internships#update
PUT /internships/:id(.:format) internships#update
DELETE /internships/:id(.:format) internships#destroy
Then you would need a Internships::FavoritesController something like:
# in app/controllers/internships/favorites_controller.rb
class Internships::FavoritesController < ApplicationController
def create
#internship = Internship.find(params[:internship_id])
current_student.favorite(#internship)
# redirect somewhere
end
def destroy
#internship = Internship.find(params[:internship_id])
current_students.unfavorite(#internship)
# redirect somewhere
end
end
Then in your view, it would be more like:
<td>
<% if current_student.favorited?(internship) %>
<%= link_to 'unfavourite', internship_favorites_path(internship), method: :delete %>
<% else %>
<%= link_to 'favourite', internship_favorites_path(internship), method: :post %>
<% end %>
</td>
Before getting into details I have read through these posts to try to find the solution without success : one, two, three
That being said: I am [new and] building an ecomm site for selling secondhand clothing, shoes and decor items.
My structure has only one Product model and associated controller and table. Each 'product' has one of three different main categories, which is what I am using to differentiate and create 3 different URLs.
My routes look like this:
Rails.application.routes.draw do
root to: 'pages#home'
get 'clothing', to: 'products#clothing'
get 'clothing/:id', to: 'products#show'
get 'shoes', to: 'products#shoes'
get 'shoes/:id', to: 'products#show'
get 'home', to: 'products#home'
get 'home/:id', to: 'products#show'
get 'products/new', to: 'products#new'
post 'products', to: 'products#create'
end
My products_controller looks like this:
class ProductsController < ApplicationController
before_action :set_all_products
before_action :set_one_product, only: [:show]
def shoes
#all_shoe_products = #all_products.where(main_category_id: MainCategory.find_by_name("shoes").id)
end
def clothing
#all_clothing_products = #all_products.where(main_category: MainCategory.find_by_name("clothes").id)
end
def home
#all_home_products = #all_products.where(main_category: MainCategory.find_by_name("housewares").id)
end
def show
end
def new
#new_product = Product.new
end
private
def set_one_product
#product = Product.find(params[:id])
end
def set_all_products
#all_products = Product.all
end
end
And when writing <%= link_to clothing_path(product) %> ('product' being the placeholder in an .each loop), I get a path: root/clothing.[:id] and not root/clothing/[:id]
I know I am making a convention error, and trying to have 3 different URLs within the same controller may be where I am gong wrong.
Note: manually entering root/clothing/[:id] in the address bar does return a product correctly.
When you do this:
get 'clothing', to: 'products#clothing'
get 'clothing/:id', to: 'products#show'
in your routes.rb, it creates these routes (which you can see by doing rake routes in your console):
clothing GET /clothing(.:format) products#clothing
GET /clothing/:id(.:format) products#show
As you can see, clothing_path routes to /clothing, not /clothing/:id. So, when you do:
<%= link_to clothing_path(product) %>
rails appends the id as .id (which is what you're experiencing).
#jvillian explains the cause of the issue well here, though I'd like to propose a slight refactor as a solution.
This might be a little more work, though you'd likely be better off with seperate controllers for shoes, clothing and home, and following a RESTful design. That would allow you to use resources in your routes file.
For example, your shoes_controller.rb would be like the following:
class ShoesController < ApplicationController
before_action :set_all_products
before_action :set_one_product, only: [:show]
def index
#all_shoe_products = #all_products.where(main_category_id: MainCategory.find_by_name("shoes").id)
end
def show
end
private
def set_one_product
#product = Product.find(params[:id])
end
def set_all_products
#all_products = Product.all
end
end
And then the routes to define them would be:
resources :shoes, only: [:index, :show]
You follow this pattern for the other resources and you'll have nicely segregated code be following good Rails conventions.
This will generate the routes as you're after:
shoes GET /shoes(.:format) shoes#index
shoe GET /shoe/:id(.:format) shoes#show
That will resolve your issue and give you a nicely designed app - there's also opportunity to extrapolate some of the code shared between the new controllers, though that sounds like a follow up task :)
Hope this helps - let me know if you've any questions or feedback.
I found a solution, though seems a bit of a logic mystery to me why it's working.
In routes.....
get 'clothing', to: 'products#clothing'
get 'clothing/:id', to: 'products#show', as: 'clothing/item'
In the index page....
<%= link_to clothing_item_path(product) do %>
This yields the right URL structure: root/clothing/[:id]
While testing this I was expecting: root/clothing/item/[:id]
...though I prefer the result over my expectation
I think what you want is parameterized routes, like this:
get ':product_line', to: 'products#index'
get ':product_line/:id', to: 'products#show'
This would allow you to create any number of custom product lines without ever having to define new methods in your controller. Assuming there is a product_line attribute on your Product model, the controller would look like this:
class ProductsController < ApplicationController
def index
#product_line = params[:product_line]
#products = Product.where(product_line: #product_line)
end
def show
#product_line = params[:product_line]
#product = Product.find(params[:id])
end
end
And your views/products/index.html.erb would look like this:
<p id="notice"><%= notice %></p>
<h1><%= #product_line %></h1>
<table>
<thead>
<tr>
<th>Description</th>
<th>Price</th>
<th></th>
</tr>
</thead>
<tbody>
<% #products.each do |product| %>
<tr>
<td><%= product.description %></td>
<td><%= product.price %></td>
<td><%= link_to 'Show', "#{#product_line}/#{product.id}" %></td>
</tr>
<% end %>
</tbody>
</table>
Note that the link_to can no longer use a Rails helper method to generate the url. You'd have to do that yourself.
The beauty of this approach is that users could type in ANY product line in the URL. If you had that product line (like say 'sporting_goods'), go ahead and display it. If not, render a page thanking them for their interest and log the fact that someone requested that product line so you can guage interest as you expand your offerings.
Plus, it's RESTful! Yay!
The Rails way of solving this is by creating a nested resource:
resources :categories do
resources :products, shallow: true
end
This nests the collection routes so that you get GET /categories/:category_id/products.
While this might not be as short as your vanity routes it is much more versatile as it will let you show the products for any potential category without bloating your codebase.
You would setup the controller as so:
class ProductsController < ApplicationController
before_action :set_category, only: [:new, :index, :create]
# GET /categories/:category_id/products
def index
#products = #category.products
end
# GET /categories/:category_id/products/new
def new
#product = #category.products.new
end
# POST /categories/:category_id/products
def new
#product = #category.products.new(product_params)
# ...
end
# ...
private
def set_category
#category = MainCategory.includes(:products)
.find_by!('id = :x OR name = :x', x: params[:id])
end
end
You can link to products of any category by using the category_products_path named path helper:
link_to "#{#category.name} products", category_products_path(category: #category)
You can also use the polymorphic path helpers:
link_to "#{#category.name} products", [#category, :products]
form_for [#category, #product]
redirect_to [#category, :products]
If you want to route the unnested GET /products and nested GET /categories/:category_id/products to different controllers a neat trick is to use the module option:
resources :products
resources :categories do
resources :products, only: [:new, :index, :create], module: :categories
end
This will route the nested routes to Categories::ProductsController.
I have two actions in the controller:
def report
#user = User.find_by_slug(params[:slug])
end
def reportForm
#user = User.find_by_slug(params[:slug])
Thread.new do
mail = ...
end
#message = 'Thanks!'
end
and in routes:
# User report form
get "/user/:slug/report", to: "users#report"
# Catch report form and action
post "/user/:slug/report", to: 'users#reportForm'
And the view:
<form method="POST" action="/user/<%= #user.slug %>/reportForm">
...
But the problem is, that when I send the form, the action reportForm is not called and instead of that is only refresh the current page with the form.
What's wrong here?
Thank you guys.
Form Helpers
The first thing that's wrong is you're not using the form helpers that Rails provides - this is a problem because you'll end up with niggly little problems like the one you're receiving:
#config/routes.rb
resources :users do
get :report #-> domain.com/users/:id/report
post :reportForm #-> domain.com/users/:id/reportForm
end
#view
<%= form_tag user_reportForm_path(#user) do %>
...
<% end %>
Routes
The second issue you have is to do with your routes
You've set the following routes:
get "/user/:slug/report", to: "users#report"
post "/user/:slug/report", to: 'users#reportForm'
This means you've got to send the request to domain.com/user/user_slug/report. Your form sends the URL to reportForm...
You should see my routes above for the solution to this problem
But more importantly, you should read up on nested resources:
#config/routes.rb
resources :users do
match :report, action: "reportForm", via: [:get, :post] #-> domain.com/users/:id/report
end
Slug
Finally, you're trying to use params[:slug] in your controller
With the resourceful routes you should be using in Rails, you'll be passing params[:id] most of the time. This should not be an issue (what is contained in params[:id] can be anything).
I would highly recommend looking at a gem called friendly_id, which makes including slugs in your application a lot simpler:
#app/models/user.rb
Class User < ActiveRecord::Base
extend FriendlyId
friendly_id :name, use: [:slugged, :finders]
end
This will allow you to call:
#app/controllers/users_controller.rb
Class UsersController < ApplicationController
def reportForm
User.find params[:id] #-> will use either `id` or `slug`
end
end
I've an issue with the paths in the views and I don't know how to solve it.
I've "categories" that has_many "posts" and "posts" that belongs_to "categories".
1.- I want to show on home page the truncate last post of an specific category (the ID number "1"). Then I want that post to link to the show post path but I get this error:
"Unknow Action
The action 'index' could not be found for PostsController"
I think I've my paths wrong because I don't need the index view because I'm only going to show that specific post. So, I think that category_posts_path(#last_post) is not the right path (I don't know where to look for more info about making the route path in the views...). Actually, the browser is showing me that is looking for the "2" category when it is a post of the "1" category...? What am I doing wrong?
This is the browser route:
http://localhost:3000/en/categories/2/posts
This is my views/categories/home.html.erb file:
<div class="post_details">
<h2><%= #last_post.title %></h2>
<%= image_tag #last_post.image(:header), class: "post_image" %>
<p><%= truncate #last_post.body, length: 100 %></p>
<p class="button"><%= link_to "READ MORE", category_posts_path(#last_post) %></p>
</div>
2.- I have another path problem in the views/categories/show.html.erb file. I have a loop to show all the post of one specific category, but when I link in some post (to show it) there is the "index" error again:
"Unknow action
The action 'index' could not be found for PostsController"
This is the browser route:
http://localhost:3000/en/categories/1/posts
This is the views/categories/show.html.erb file:
<div class="post_details">
<h2><%= link_to post.title, category_posts_path(post) %></h2>
<%= image_tag post.image(:header), class: "post_image" %>
<p><%= post.body %></p>
</div>
This is the categories_controller.rb file:
class CategoriesController < ApplicationController
before_action :get_categories
def index
end
def show
#category = Category.find(params[:id])
end
def home
if params[:set_locale]
redirect_to root_url(locale: params[:set_locale])
else
#category = Category.find_by_id(1)
#last_post = #category.posts.order("created_at desc").first
end
end
def get_categories
#categories = Category.all.order("rank asc, name asc")
end
end
This is my posts_controller.rb file:
class PostsController < ApplicationController
def show
#category = Category.find(params[:category_id])
#post = #category.posts.find(params[:id])
end
end
This is my route.rb file:
scope '(:locale)' do
resources :categories do
resources :posts
end
resources :contacts
root 'categories#home'
get "/contact" => "contacts#new"
# static pages
get "/investment" => "contents#investment"
get "/partner-with-us" => "contents#partner", as: "partner"
get "/our-companies" => "contents#companies", as: "companies"
get "/site-map" => "contents#sitemap", as: "sitemap"
get "/terms-and-conditions" => "contents#terms", as: "terms"
get "/privacy" => "contents#privacy"
end
When you are nesting routes you should always consider what is the parent and whats a child in given route. Since your paths don't know anything about your associations you have to explicitly define every object in the nesting.
I.e. since you nested posts in categories linking to last post in given category would look like this:
category_post_path(#category, #last_post)
(I think you have also a typo there - category_posts_paths - which links to posts index index - hence the error. Use category_post_path. instead, and give it both parent category and the post.
You can run rake routes to see exact information on paths (or go to http://localhost:3000/rails/info/routes )