rails controller for mixed nested and non-nested resources - ruby-on-rails

I'm working on a blog like application,
my user module has_many posts and the posts module belongs_to user
I want to access both users/:id/posts and posts/
routes.rb is something like this:
resources :users do
resources :posts
end
resources:posts
how can i know within the posts controller if its accessed directly (/posts) or through the nested route (/users/:id/posts) ?
for example, what should be the index method of the posts controller for doing the correct INDEX action for /users/:id/posts and for /posts
is there a better way for doing this ?

One solution could be to use a before filter on your controller, like:
before_filter :load_user
def load_user
#user = User.find(params[:user_id]) if params[:user_id]
#posts = #user ? #user.posts : Post.all
end
Then you have to rewrite your controller a bit to function properly.
No refactoring needed on index action, #posts already loaded correctly, but you can do further filtering as you like
def index
#posts = #posts.where('updated_at < ?' Time.now)
end
Then update every member action: new, create, show, edit, update, destroy and use posts as a base like:
def new
#post = #posts.build
end
def create
#post = #posts.build(params[:task])
end
def show
#post = #posts.find(params[:id])
end
def edit
#post = #posts.find(params[:id])
end
def update
#post = #posts.find(params[:id])
end
def destroy
#post = #posts.find(params[:id])
end
Of course you can add other before filters to remove duplicate code.

Check the params.
If just post you'll just have :id
If user/post you'll have user and ID for post.
So check if params[:user]...
n.b. If not user, try params[:user_id]
As for the index method for posts I think it will actually be the SAME in both cases. What will change things is its usage, association and scoping within user.

Related

Passing in parameter to new_path and increasing the count on a create

I've recently been working with controllers and routing. Typically when I write my new and create routes, they are:
get '/pages/new' => 'pages#new', as: :new_page
post '/pages/ => 'pages#create'
and the controller actions are:
def new
#page = Page.new
end
def create
#page = Page.new(page_params)
if #page.save
flash[:notice] = "Successfully created page."
redirect_to page_path(#page)
else
render action: 'new'
end
end
private
def page_params
params.require(:page).permit(:book_id, :text, :page_number)
end
So that works for new_page_path and post_pages_path.
But what if did new_page_path(book_id: #book.id)? (Also a page belongs to a book). What does this mean and how does that change my new and create methods? Also, if I were to create a new page, how would I change the count of the number of pages in my book?
I don't know how your models look like, but i suppose that you've got association: has_many.
If so, you might want to use nested resources in routes https://guides.rubyonrails.org/routing.html#nested-resources
resources :books do
resources :pages
end
that gives you always:book_id in params for pages actions. You can easily create a page associated with the book than by:
#book = Book.find params[:book_id]
#book.pages.build(pages_params)
And you don't really need to handle increasing count on create, if you properly set association. book.pages.count will tell you the truth.

About nested resources with just resources in rails

The problem is i want to have both nested resources and normal resources pointing the the same action in controller but act differently based on whether the nesting resource in available or not.
Routes.rb
resources :users do
resources :comments //For having nested routes
end
resources :comments //For having normal routes
Nested Resource
For example
Class CommentsController < ApplicationController
def index
#user = User.find(params[:id])
#comment = #user.comments.find(params[:id])
end
Now,
If i want to just look up all the comments , i want to use the
/comments
Else if i am in the user page and click on the all comments i get to
/user/:user_id/comments
Hence, how to configure to make sure it generates the right page.
If I were you, I would do like this:
def index
#comment = Comment.find_by(user_id: params[:user_id], id: params[:id])
# It looked weird that you tried to find a #comment
# instead of #comments in action `index`
# I think you made a typing mistake and this can be help
#comments = Comment.where(user_id: params[:user_id])
end

With rails before_action how can you protect from malicious users for create / update / edit and delete

The rails before action seems useful for setting a variable shared by a number of actions in a controller.
But isn't the default implementation of the set_post that we see commonly on tutorials etc open to an attack by a malicious user?
If we take a controller like this:
PostsController < Application Controller
before_action :set_post , only: [:show,:create,:update]
def show
...
end
def create
...
end
def update
...
end
private
def set_post
#post = Post.find(params[:id])
end
end
When a user is presented the opportunity to update a post for example the form would be generated for them, and on post, params[:id] would contain the ID of the appropiate post - probably owned by the current_user.
However, it would not be difficult for a malicious user to alter the posted :id variable to allow them to actually end up setting the #post variable in the controller, to represent a different post, rather than the original being updated.
I could see this being safer:
private
def set_post
#post = Post.find(params[:id])
if(#post.user_id != current_user.id)
redirect_to homepage, alert: "you can edit your own posts"
end
end
However - that would stop other users viewing other people's posts! So how and where should this kind of check be performed to ensure that only the owner of a particular post can update / edit it. Is that something for the update controller action to handle itself with a check like this :
def update
if #post.user_id != current_user.id
redirect_to homepage, alert: "you can edit your own posts"
end
...
end
You are right, and I actually see that security issue being made very often by newbie Rails programmers. They just generate scaffolds and don't change things to their needs.
I'm using something like the following in my controllers:
before_action :set_post
before_action :check_post_ownership, except: :show
private
def set_post
#post = Post.find(params[:id])
end
def check_post_ownership
redirect_to homepage, alert: "..." unless #post.user_id == current_user.id
end

Rails - Listing records of a model in a static page

I am trying to call records of a model (listing all records like Posts.all) to a view that is binded to another controller.
So, I want to reach the index action of Posts_controller which contains the .all listing and .group_by listing that I want to reach and list them in a static page that is listed in Pages_controller (named as yonetim)
This is just for listing the posts for admin view (like the listing in active admin).
I think, I dont need to post any code because the question is quite abstract, but if needed I will edit the question.
* Edit for clarifying*
This is my posts_controller.rb
class PostsController < ApplicationController
before_action :find_post, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!, except: [:index, :show]
load_and_authorize_resource
def index
#posts = Post.all.order('postdate DESC')
#posts_by_month = #posts.group_by { |post| post.postdate.strftime('%m - %Y')}
end
def show
end
def new
#post = current_user.posts.build
end
def create
#post = current_user.posts.build(post_params)
if #post.save
redirect_to #post
else
render 'new'
end
end
def edit
end
def update
if #post.update(post_params)
redirect_to #post
else
render 'edit'
end
end
def destroy
#post.destroy
redirect_to root_path
end
private
def post_params
params.require(:post).permit(:id, :title, :body, :postdate)
end
def find_post
#post = Post.find(params[:id])
end
end
As it can be seen it is a basic blog application. Visitors that reach the root_path (posts#index routed) can see the post records based on grouping of month and year.
What I want to add is reaching the new, edit destroy and index.#posts from a static page that I create for the purpose of admin interface (similar to active Admin gem).
** This is the pages_controller.rb **
class PagesController < ApplicationController
def yonetim
end
end
So when I hit /yonetim (routed to get pages#yonetim), I want the user to see the index action of posts controller with link to new, show, edit and destroy of the record.
***The system also have devise with admin boolean and cancan so if the user not signed in or don't have the authorization for admin usage, they are moved to root_path with an exception.
My problem rises that, I have tried nearly everything to list the #posts records of posts#index method in the pages/yonetim view or in the pages_controller.rb yonetim method.
So that I can list them in my admin view and work around with them.
If anything else is required just let me know.
Thanks in advance,
Mustafa
Multiple options here for pages#yonetim:
Just Redirect to 'posts#index'
Assign view variables (#posts and #posts_by_month) like in posts#index and render template posts/index.
Assign view variables (#posts and #posts_by_month) like in posts#index and render template pages/index (the default view in this case).
The drawback for the first two options is that all links (new/edit/destroy) will link to the PostsController and not to the PagesController, because you are re-using the views that were created for the PostsController.

Creating a form for a different controller

I have several books that users can create and then create pages for them.
routes.rb:
resources :books do
member do
get 'pages'
end
end
This gives a link to create a new page through /books/:book_id/pages
In that page I render a partial:
<%= render '/pages/new' %>
book controller:
def page
#book = Book.find(params[:id])
#page = #book.pages.new
end
But when params are passed to a page controller the book_id is lost:
pages controller:
def create
#page = Page.new(params[:page])
if #page.save
redirect_to #page
else
render 'new'
end
end
Also error handling becomes difficult as after the redirect the id also disappears.
I believe rest of the application seems fine as when I create the page through console the book_id is preserved.
It is my first app so I'm not even sure if this is even the right way to approach it... How can I get this to work?
Thanks guys!
Nested resources can be handled in such a way.
# config/routes.rb
resources :books do
resources :pages
end
You can also restrict the actions being created by using only: [:index, :new] or except: [:destroy] on the resources call.
In your PagesController you would have the following params available.
GET /books/:book_id/pages/:id => creates a params[:book_id] and params[:id]
# app/controllers/pages_controller.rb
def create
#book = Book.find(params[:book_id])
#page = #book.pages.build(params[:page])
if #page.save
# ...
else
# ..
end
end
Note that the method call for building a new object through the association is #book.pages.build instead of .new. You'll find out all about Rails associations and how to build and create the objects through association in the Rails Guides on ActiveRecord associations. Hope these pointers help.
If you want to debug incoming parameters, you can just raise them as C404 suggested above.
# in any controller
def any_method
raise params.inspect
# or in yaml format
raise params.to_yaml
end
One way to debug this is to use "puts" in your controller for params to see what actually exists. "puts" are like println's in java or echo's in PHP
Please post the contents of params when ran with this code
def create
puts "params = #{params}"
#page = Page.new(params[:page])
if #page.save
redirect_to #page
else
render 'new'
end
end

Resources