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
Related
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.
I'm wondering how can I print on the index of my project only the rooms with the :is_available column or the rooms table with the :true value (is boolean).
I can't figure out how to achieve this (Sorry but I'm new with Rails). Any advice will be very appreciate!
I've this error with my current code:
"ActiveRecord::RecordNotFound in RoomsController#home
Couldn't find Room without an ID"
Here is my rooms_controller code:
class RoomsController < ApplicationController
before_action :get_room, only: [:index, :home]
def index
end
def show
#room = Room.find(params[:id])
end
def home
if params[:set_locale]
redirect_to root_url(locale: params[:set_locale])
else
puts #rooms if Room.all(params[:is_available => :true])
end
end
def get_room
#rooms = Room.all
end
end
You already have got #rooms = Room.all, you just need to precise your query (from all to your is_available restriction).
def home
if params[:set_locale]
redirect_to root_url(locale: params[:set_locale])
else
puts #rooms.where(is_available: true)
end
end
Also, you should avoid using puts in your controller logic. Either pass variable to the view (you can change #rooms value or create new variable #available_rooms), respond_with it or log it using Rails.logger if you use puts as a debugging solution.
def index
end
def home
if params[:set_locale]
redirect_to root_url(locale: params[:set_locale])
elsif params[:is_available]
puts #rooms
end
end
def get_room
#rooms = Room.where(is_available: true)
end
Using puts in controller - not a good idea.Use view to show the data.
There are several issues you may have:
Routes
Your index method looks empty. I presume you're using "home" as a substitute
In this case, you have to know what type of action this is - a member or collection action? The reason this is important is that when you define your routes, you have to ensure you define the route in the right way. For your home route, I'd have done this:
#config/routes.rb
resources :rooms do
get "home", action: "home"
end
Scopes
You can use a scope to bring back all the values with :is_available present. This lives in the model like this:
#app/models/room.rb
Class Room < ActiveRecord::Base
scope :is_available?, -> { where(is_available: true) }
end
This will allow you to call
#room = Room.is_available?
Code
Although you've not given us any context of the error (when it happens, what you do to make it happen), this is what I would do to help fix it:
#app/controllers/rooms_controller.rb
def home
if params[:set_locale]
redirect_to root_url(locale: params[:set_locale])
else
puts Room.is_available?
end
end
This may change depending the params you send & how you send them
def home
if params[:set_locale]
redirect_to root_url(locale: params[:set_locale])
else
puts #rooms if params[:is_available] && Room.where(is_available: true)
end
end
should work.
For example: can I render '/tags/:id/posts'?
If I try to render tag_posts_path(#tag) or some other ways, I get this or similar error:
Missing partial /tags/1/posts...
In my routes.rb I have this:
resources :tags do
resources :posts
end
I've stucked a bit with thing like yours
I achieved rendering correct page
def create
#project = Project.find(params[:project_id])
#comment = #project.comments.build(comment_params)
if #comment.save
flash[:success] = "Chingon!"
redirect_to #project
else
render 'projects/show'
end
end
My routes looks like:
resources :projects do
resources :comments, only: [:create, :destroy]
end
Rails expecting you to render not path but some page(like 'new' or 'show')
I got trouble only with my ELSE where I'm rendering 'projects/show'. It seem to be not quite right template, coz it renders in raw, without any CSS but with errors I expect to appear. It's the only way for now that I can render error messages for empty comment.
I walk over that issue simply by bypassing default way to display errors and place it in simple flash like this:
def create
#project = Project.find(params[:project_id])
#comment = #project.comments.build(comment_params)
if #comment.save
flash[:success] = "New comment here, bro"
redirect_to #project
else
redirect_to #project
flash[:error] = flash_error_message(#comment)
end
end
def flash_error_message(arg)
"The form contains #{arg.errors.count} error: #{arg.errors.full_messages.join(', ')}"
end
If I'm correct, I think what you are looking for is something like this
resources :tags do
resources :posts
end
You can see what kind of urls or routes this generates by typing in rake routes
I'm not really sure but, re-reading your question I think you mean rendering by saying
render 'some nested route'
The above routes I provided would allow you to do something like this...
render tag_posts_path(#tag) that will in term look for a index action since its the posts path. Again, rake routes shows all this.
A quick search on google for nested routes would've led you to this.
Nested Routes
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.
I have a scenario being:
resources :magazines do
resources :articles do
resources :comments
end
end
So as to avoid nesting more than 2 levels deep I have re-factored this to be:
resources :magazines do
resources :articles
end
resources :articles do
resources :comments
end
My article show action URL is:
/magazines/3/articles/11
In this view I have a form for creating a new comment.
When a comment is saved successfully the form redirects which all works well.
When the form submission is not successful I wish to redisplay the view with validations errors displayed. I understand the correct way to do this is to render the 'articles/show' view. This also works and the view is redisplayed with the validation errors shown.
The problem is when the save fails and articles/show is rendered the URL is no longer correct and is shown as:
/articles/11/comments
class ArticlesController < ApplicationController
def show
#article = Article.find(params[:id])
#comments = #article.comments.order(created_at: :asc).page(params[:page]).per_page(5)
#comment = Comment.new
end
end
class CommentsController < ApplicationController
def create
#article = Article.find(params[:id])
#comment = #article.comments.new(discussion_params)
#comment.user_id = current_user.id
if #comment.save
redirect_to #article
else
render 'articles/show'
end
end
private
def discussion_params
params.require(:comment).permit(:content)
end
end
I solved this by changing my routes back to the way it originally was and now the article show action includes the magazine in the url.
I understand this breaks the "no more than 2 levels deep" routing rule but it's the only way I can get it to work.