Rails - Multiple Views(admin, regular) from same action(index) - ruby-on-rails

I have 2 versions of an same index page which display a list of products.
Regular view - Displays List
Admin view - Displays List with options to edit, delete etc.
The index action just makes the database query and returns that instance variable.
Currently, I have the same index action for rendering the index page. I want to reuse the index action form product into the admin controller.
def index
#products = Product.all
end
routes:
/product/index => product#index
/admin/product/index => product#index
I tried the prepend_view_path technique given [here | How can I intercept rails's template rendering, but then this always ends up rendering the admin/product/index.html.erb file in both cases.
I want the Product#index controller#action to render:
/index.html.erb for /product/index
and
/admin/product/index.html.erb for /admin/product/index
Can someone help me with how this could be done in an elegant way instead of just writing the same action for admin controller class and product controller class.
PS: I'm just one week into ruby and ruby on rails. Any help is much appreciated.

There is a Mistake in routes .
/product/index => product#index
/admin/product/index => product#index
Both pointing to the same contoller names as product.
I would suggest these routes
resources :admin do
resources :product do
end
end
resources :product do
end
By doing so you make sure that you have two products Controller.
One with Admin::ProductController
Second ProductController
admin_product_path for admin view
product_path for normal view

From within your controllers, just render view you want to serve for user, I mean:
ProductsController
def index
# ...
render 'index'
end
Admin::ProductsController
def index
# ...
render 'products/index'
end

I faced this challenge when working on a Rails 6 application.
I had two types of users: Customers and Admins. I was using the Devise gem for authentication. I wanted the Products views for the Customer to be different from the Admins View.
I already had an app/controllers/admins directory in the controllers for the Devise configuration for Admins.
Here's how I did it:
First, define a new route for the Admins products view using the admins namespace.
namespace :admins do
resources :products do
end
end
Note: This will affect the Paths/URLs for the Admins. Say, the products_path will not be admins_products_path.
Then, in the controllers add the products_controller.rb to the app/controllers/admins directory:
class Admins::ProductsController < ApplicationController
before_action :set_product, only: [:show, :edit, :update, :destroy]
# GET /products
# GET /products.json
def index
#products = Product.all
end
# GET /products/1
# GET /products/1.json
def show
end
# GET /products/new
def new
#product = Product.new
end
# GET /products/1/edit
def edit
end
# POST /products
# POST /products.json
def create
#product = Product.new(product_params)
respond_to do |format|
if #product.save
format.html { redirect_to admins_product_path(#product), notice: 'Product was successfully created.' }
format.json { render :show, status: :created, location: admins_product_path(#product) }
else
format.html { render :new }
format.json { render json: #product.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /products/1
# PATCH/PUT /products/1.json
def update
respond_to do |format|
if #product.update(product_params)
format.html { redirect_to admins_product_path(#product), notice: 'Product was successfully updated.' }
format.json { render :show, status: :ok, location: admins_product_path(#product) }
else
format.html { render :edit }
format.json { render json: #product.errors, status: :unprocessable_entity }
end
end
end
# DELETE /products/1
# DELETE /products/1.json
def destroy
#product.destroy
respond_to do |format|
format.html { redirect_to admins_products_url, notice: 'Product was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_product
#product = Product.find(params[:id])
end
# Only allow a list of trusted parameters through.
def product_params
params.require(:product).permit(:name, :sku, :short_description, :full_description)
end
end
Note: Take note of the ProductsController namespacing using the Admins Module and the modified paths in the create, update and destroy actions od the
And finally, in the views, add the views associated with the Admins Products in the app/views/admins/products directory.
Note: You may have to modify the paths in the views to correspond with the routes for the admins products. Say, the show view for admins products will be admins_product_path(product) and not product or product_path(product).
There is a cleaner approach using the cells gem. This eliminates the need to duplicate code, and it comes in handy when you need to define views for up to 3 or more roles. You can read up more about how to use it here: Object-Oriented Views in Rails.
That's all.
I hope this helps

Related

rails issue with naming convention and table

I have an issue with rails with naming convention.
I have a database where i can't rename table so names are not in plural with inflector.
Today i wanted create model and controller for the table "wishlist__c" and the issue is here. I tried 3 times first by duplicating product model, controller.... and changing name then creating files myself and i still got the issue and then with rails g scaffold wishlist__c
The first error when i try to go to url:8080/wishlist__c/index :
Routing Error
uninitialized constant WishlistCController
wishlist__c_controller.rb exist. I notice after many test that the double '__' is a problem in rails. I rename it to wishlist_c_controller and the same with the model. the error message change to
--Solution: I forget to rename folder wishlist__c to wishlist_c in views folder
Thanks you all ! --
ActiveRecord::RecordNotFound in WishlistCController#show
Couldn't find WishlistC with 'id'=index
the code display under this is from wishlist_c_controller.rb:
def set_wishlist__c
#wishlist__c = ::WishlistC.find(params[:id])
end
How to solve it. I need to link my app to this table
edit:
Model wishlist_c.rb:
class WishlistC < ApplicationRecord
self.table_name = "wishlist__c"
end
wishlist_c_controller:
class WishlistCController < ApplicationController
before_action :set_wishlist__c, only: [:show, :edit, :update, :destroy]
# GET /wishlist__c
# GET /wishlist__c.json
def index
#wishlist__c = WishlistC.all
end
# GET /wishlist__c/1
# GET /wishlist__c/1.json
def show
end
# GET /wishlist__c/new
def new
#wishlist__c = WishlistC.new
end
# GET /wishlist__c/1/edit
def edit
end
# POST /wishlist__c
# POST /wishlist__c.json
def create
#wishlist__c = WishlistC.new(wishlist__c_params)
respond_to do |format|
if #wishlist__c.save
format.html { redirect_to #wishlist__c, notice: 'Wishlist c was successfully created.' }
format.json { render :show, status: :created, location: #wishlist__c }
else
format.html { render :new }
format.json { render json: #wishlist__c.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /wishlist__c/1
# PATCH/PUT /wishlist__c/1.json
def update
respond_to do |format|
if #wishlist__c.update(wishlist__c_params)
format.html { redirect_to #wishlist__c, notice: 'Wishlist c was successfully updated.' }
format.json { render :show, status: :ok, location: #wishlist__c }
else
format.html { render :edit }
format.json { render json: #wishlist__c.errors, status: :unprocessable_entity }
end
end
end
# DELETE /wishlist__c/1
# DELETE /wishlist__c/1.json
def destroy
#wishlist__c.destroy
respond_to do |format|
format.html { redirect_to wishlist__c_index_url, notice: 'Wishlist c was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_wishlist__c
#wishlist__c = WishlistC.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def wishlist__c_params
params.fetch(:wishlist__c, {})
end
end
Rails create RESTful routes based on controller and model. One of the routes would be get wishlist__c/:id which gets mapped to action show of WishlistCController. so when you hit the URL wishlist__c/index it takes index as the id.
If you want to render index page, create a route get wishlist__c/index and map it to index method of your controller. For the above to work you must hit the URL url:8080/wishlist__c/1 where 1 is your WishList ID. Replace it with values of id column of wishlist__c table.
Looking at your controller, you already have a route get wishlist__c/ mapped to the index method of your controller. So url:8080/wishlist__c/ should render index page for your model.

param is missing or the value is empty: module_list

I'm trying to enter a list of new modules and when I press 'new module list' which should take me to the form to fill out it throws up the error from the title. The application trace points at the bottom, the code inside 'def module_list_params' and also just above it where 'def set_student' is. I have no idea why it's doing it. I'm using ruby on rails.
class ModuleListsController < ApplicationController
before_action :set_module_list, only: [:show, :edit, :update, :destroy]
before_action :set_student, only: [:new, :create]
# GET /module_lists
# GET /module_lists.json
def index
#module_lists = ModuleList.all
end
# GET /module_lists/1
# GET /module_lists/1.json
def show
end
# GET /module_lists/new
def new
#module_list = #student.module_lists.new
end
# GET /module_lists/1/edit
def edit
end
# POST /module_lists
# POST /module_lists.json
def create
#module_list = #student.module_lists.new(module_list_params)
respond_to do |format|
if #module_list.save
format.html { redirect_to #module_list, notice: 'Module successfully created.' }
format.json { render :show, status: :created, location: #module_list }
else
format.html { render :new }
format.json { render json: #module_list.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /module_lists/1
# PATCH/PUT /module_lists/1.json
def update
respond_to do |format|
if #module_list.update(module_list_params)
format.html { redirect_to #module_list, notice: 'Module list was successfully updated.' }
format.json { render :show, status: :ok, location: #module_list }
else
format.html { render :edit }
format.json { render json: #module_list.errors, status: :unprocessable_entity }
end
end
end
# DELETE /module_lists/1
# DELETE /module_lists/1.json
def destroy
#module_list.destroy
respond_to do |format|
format.html { redirect_to module_lists_url, notice: 'Module list was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_module_list
#module_list = ModuleList.find(params[:id])
end
def module_list_params
params.require(:module_list).permit(:student_id, :title, :description, :credit_value)
end
def set_student
#student = Student.find_by(id: params[:student_id]) ||
Student.find(module_list_params[:student_id])
end
end
Rake routes screenshot
I believe your issue is the line before_action :set_student, only: [:new, :create]. set_student is being run when you go to the page with the form, but since there is no student_id included in the URL, it can't find anything to set it to.
To create a dependent object, there are two main ways: you can either have a form page tied to a specific parent object already, ie /students/4/module_lists/new, in which case submitting the form will create a module list tied to the student with an ID of 4. The other way is to have a general form not tied to any specific parent object, with some way of selecting a parent inside the form, eg a select or something. In that case the url would just be something like /module_lists/new.
If you want to go the first route, you'll want to nest the resources :module_lists inside of students. Check out the docs for how to do that, but it would basically look like
resources :students do
resources :module_list
end
And then in the link_to you click to go to that page, you'll need to pass in the student_id:
link_to 'Create Module List', new_student_module_list_path(#student)
For the second option, you can just remove :new from the before_action, change the new method to
def new
#module_list = ModuleList.new
end
And then add a way of picking which student to tie it to to the form.

Getting the ID of a Model that Uses Nested Routes and Permalinks in Rails

So I had my app set up with ids like so:
resources :studios do
resources :bookings
end
This gave me the route to the index (which later I'm going to use json for to get calendars for each studio.
studio_bookings GET /studios/:studio_id/bookings(.:format) bookings#index
This is good, but I wanted to get rid of the ID and use a permalink instead, just for a friendlier URL.
Change to:
namespace :studio, :path =>'/:permalink' do
resources :bookings
end
Now I'm getting
studio_bookings GET /:permalink/bookings(.:format) studio/bookings#index
Great! this is how I want my url to look, however, now the :id isn't anywhere in the route so... I get
Couldn't find Booking without an ID
It isn't even being passed. Is there a way to pass the :id in with the url without it being actually USED in the url? Otherwise, do I change the primary key from :id to :permalink in order to fix this?
I tried changing my controller from
#studio = Studio.find(params[:id])
to
#studio = Studio.find(params[:permalink])
but that gives me
Couldn't find Booking with 'id'=40frost
Which tells me what I'm doing isn't really meant to be done? It's trying to put the permalink as the id, so even though I'm telling rails to look for the permalink, it's still seemingly looking it up as an ID.
Hopefully my problem is clear: essentially - how can I pass the id so it knows which studio without displaying it in the URL. If there's some controller magic I can do instead that would be convenient.
Here's my controller for good measure
class Studio::BookingsController < ApplicationController
before_action :set_booking, only: [:show, :edit, :update, :destroy]
# GET /bookings
# GET /bookings.json
def index
#studio = Studio.find(params[:permalink])
#bookings = Booking.where("studio_id => '#studio.id'")
end
# GET /bookings/1
# GET /bookings/1.json
def show
end
# GET /bookings/new
def new
#booking = Booking.new
end
# GET /bookings/1/edit
def edit
end
# POST /bookings
# POST /bookings.json
def create
#booking = Booking.new(booking_params)
respond_to do |format|
if #booking.save
format.html { redirect_to #booking, notice: 'Booking was successfully created.' }
format.json { render action: 'show', status: :created, location: #booking }
else
format.html { render action: 'new' }
format.json { render json: #booking.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /bookings/1
# PATCH/PUT /bookings/1.json
def update
respond_to do |format|
if #booking.update(booking_params)
format.html { redirect_to #booking, notice: 'Booking was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: #booking.errors, status: :unprocessable_entity }
end
end
end
# DELETE /bookings/1
# DELETE /bookings/1.json
def destroy
#booking.destroy
respond_to do |format|
format.html { redirect_to bookings_url }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_booking
#booking = Booking.find(params[:permalink])
end
# Never trust parameters from the scary internet, only allow the white list through.
def booking_params
params.require(:booking).permit(:start_time, :end_time, :studio_id, :engineer_id, :title, :allDay)
end
end
You could just do
self.primary_key = 'permalink'
in your Studio model, or you could do
def index
#studio = Studio.find_by permalink: params[:permalink]
#bookings = Booking.where(studio_id: #studio.id)
end
depends if you just want to locally change the behavior or adress the Studio model by permalink always.
Hope that helps!

Rails 4 : How to disable the Edit, Destroy etc,

Can I disable the "Edit" and "Destory" in the Rails ?for example, if I want to disable the "Edit" for everyone,what show I do in the test_controller.rb ? or anything else?
I am new to Rails, thanks in advance!
class BooksController < ApplicationController
before_action :set_book, only: [:show, :edit, :update,:destroy ]
# GET /books
# GET /books.json
def index
#books = Book.all
end
# GET /books/1
# GET /books/1.json
def show
end
# GET /books/new
def new
#book = Book.new
end
# GET /books/1/edit
def edit
end
# POST /books
# POST /books.json
def create
#book = Book.new(book_params)
respond_to do |format|
if #book.save
format.html { redirect_to #book, notice: 'Book was successfully created.' }
format.json { render :show, status: :created, location: #book }
else
format.html { render :new }
format.json { render json: #book.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /books/1
# PATCH/PUT /books/1.json
def update
respond_to do |format|
if #book.update(book_params)
format.html { redirect_to #book, notice: 'Book was successfully updated.' }
format.json { render :show, status: :ok, location: #book }
else
format.html { render :edit }
format.json { render json: #book.errors, status: :unprocessable_entity }
end
end
end
# DELETE /books/1
# DELETE /books/1.json
def destroy
#book.destroy
respond_to do |format|
format.html { redirect_to books_url, notice: 'Book was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_book
#book = Book.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def book_params
params.require(:book).permit(:name, :author, :price)
end
end
`Rails.application.routes.draw do
resources :books
root :to => "home#index"
get 'home/index'
end`
You can restrict the restful routes to make edit and destroy actions inaccessible.
In your routes.rb,
resources :books, except: [:edit, :destroy]
See: http://guides.rubyonrails.org/routing.html#restricting-the-routes-created
EDIT
If you want to keep to the RESTful routes (so that you don't have to modify code in your views), you can use before_action in controller to redirect users.
before_action :redirect_user, only: [:edit,:destroy]
def redirect_user
redirect_to root_path
end
This approach is generally used when you want to restrict access to certain actions based on some condition.
For example, if you want only admins to edit and remove books, you can have condition inside redirect_user that checks if current user is admin or not and redirects non-admin users.
You should look in to the cancancan gem.
https://github.com/CanCanCommunity/cancancan
It's an authorization library for Ruby on Rails which restricts what resources a given user is allowed to access. So you can create an admin class, and only allow administrators to edit and destroy. Its pretty simple to use and works well with devise.

How to list only current user orders if not an admin (Ruby on Rails)?

I'm pretty new to rails and I'm just now developing my first rails app, so this might be a dumb question to some. I would like to let the current_user see only their own orders if they are not an admin. I was able to set only admins can see all orders, but I'm having a hard time enabling current user to see, list and delete only their own orders. My app has a :orders model that belongs_to :users and a :users model with has_many :orders.
This is how my orders_controller.rb look like:
class OrdersController < ApplicationController
before_action :set_order, only: [:show, :edit, :update, :destroy]
# GET /orders
# GET /orders.json
def index
authorize! :index, #user, :message => 'Not authorized as an administrator.'
#orders = Order.all
end
# GET /orders/1
# GET /orders/1.json
def show
end
# GET /orders/new
def new
#order = Order.new
end
# GET /orders/1/edit
def edit
end
# POST /orders
# POST /orders.json
def create
#order = Order.new(order_params)
respond_to do |format|
if #order.save
format.html { redirect_to #order, notice: 'Order was successfully created.' }
format.json { render action: 'show', status: :created, location: #order }
else
format.html { render action: 'new' }
format.json { render json: #order.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /orders/1
# PATCH/PUT /orders/1.json
def update
respond_to do |format|
if #order.update(order_params)
format.html { redirect_to #order, notice: 'Order was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: #order.errors, status: :unprocessable_entity }
end
end
end
# DELETE /orders/1
# DELETE /orders/1.json
def destroy
#order.destroy
respond_to do |format|
format.html { redirect_to orders_url }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_order
#order = Order.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def order_params
params.require(:order).permit(:user_id, :drop_address)
end
end
My question is how do I allow only the current user to list and see all orders made by them only?
Thanks
There is gem named cancan for you.
Please read wiki page.
Need more help? let me know :)
define ability
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.admin?
can :manage, :all
else
can :read, Order, :user_id => user.id
end
end
end
from controller query by accessible_by
#orders = Order.accessible_by(current_ability)
you have to do it on two levels. In index you have to fetch orders for the current users so users can only see his orders. the second level is you make sure that the user may enter an order url that doesnt belong to him, so check for that in the other actions(edit,update,delete,show).
Or you can use declarative authorization gem. it is very helpful https://github.com/stffn/declarative_authorization
-hint: for naming conventions change belongs_to :users in order model to belongs_to :user (belongs_to is always singular)
This is how your controller should look like
#this is the filter called before each method to make sure the user is authorized to access the order
before_filter :authorize_user, :only => [:edit,:update,:delete,:show]
def index
authorize! :index, #user, :message => 'Not authorized as an administrator.'
#here fetch the orders of the current user only
#orders = current_user.orders
end
#and then goes all your methods here as normal
private
def authorize_user
unless current_user.eql?(#order.user)
flash[:notice]="You are not authorized to access this order"
redirect_to orders_path
end
end

Resources