This question already has an answer here:
SystemStackError (stack level too deep)
(1 answer)
Closed 7 years ago.
I'm pretty new to Ruby on Rails and i'm trying to build a simple blogging platform that allows user authentication using devise. When I fill out the posts form which takes a title and content field, when I press the "create post" button i get an error that reads
SystemStackError in PostsController#create stack level too deep
It seems as if the "create" action in my PostsController is where the problem stems from. Can any one help me? This is how my PostsController looks:
class PostsController < ApplicationController
before_action :authenticate_user!
def index
#posts = Post.all
end
def new
#post = Post.new
end
def create
#post = Post.new(params[:post])
end
def show
#post = Post.find(params[:id])
end
private
def params
params.require(:post).permit(:title, :content)
end
end
Easy, you call params method in the body of params method (recursively) - boom, infinite loop. Change the name of your custom params method:
def create
#post = Post.new(post_params)
# etc.
end
# ...
def post_params
params.require(:post).permit(:title, :content)
end
Related
I'm using rails_best_practices for linting my code.
Comments belong to post, which belongs to the user.
My comments_controller.rb file looks like this
class CommentsController < ApplicationController
before_action :find_post
def create
#comment = #post.comments.create comment_params
#comment.user_id = current_user.id
redirect_to #post if #comment.save
end
private
def find_post
#post = Post.find(params[:post_id])
end
def comment_params
params.require(:comment).permit(:post_id, :body)
end
end
And I'm getting this error use model association (for #comment).
After refactoring my create method looks like this
def create
#comment = #post.comments.create(
comment_params.merge(user_id: current_user.id)
)
redirect_to #post if #comment.save
end
My question is: What is the best and correct way to do this?
Normally I'd suggest baking in any required parameters inside your controller-specific _params function. That is, do this:
def comment_params
params.require(:comment).permit(:post_id, :body).merge(
user: current_user
)
end
Then by the time it gets to your controller action you're pretty much good to go.
What I tend to do is have a build method that constructs the right object for both new and create:
def build_comment
#comment = #post.comments.build(comment_params)
end
Now this will correctly populate if you relax the require constraint on params, but it's up to you how to make that flexible. I find this consistently populates and prepares the same object for both multiple edit rounds, and the first round where you need to set some defaults.
I'm writing a simple website in which users (authenticated with Devise) can create posts. Upon creating a new post, I'm running into this error in which it won't redirect to the post. Here's my Posts controller:
class PostsController < ApplicationController
before_filter :authenticate_user!
def new
#post = current_user.posts.new
end
def create
#post = current_user.posts.new(post_params)
if #post.save
redirect_to #post
end
end
def show
#post = Post.find_by_id(params[:id])
end
private
def post_params
params.require(:post).permit(:title, :content)
end
end
Here's my routes.rb:
Rails.application.routes.draw do
devise_for :users
resources :users do
resources :posts
end
root 'posts#new'
end
Since the posts resource is nested within users I thought perhaps I should have this in my controller:
if #post.save
redirect_to current_user.#post
end
But that produces a SyntaxError in PostsController#create error.
Can anyone see the problem that's preventing the controller from redirecting to the post after it's created? Any help would be much appreciated.
Try this -
redirect_to [current_user,#post]
OR,
redirect_to user_post_path(current_user, #post)
hope it helps!
In your model, have you allowed for nested attributes? In the controller you want to use build instead of new.
So I got a very basic blog app running with three links to a blog post. However, when I click on a post of my selection and edit the post and click on "update blog", I will get an error saying NameError in BlogsController#update and undefined local variable or method 'blog_params' for blogscontroller. I cannot figure out what the issue is so I would like some help to guide me through
This is what my blogs controller file looks like
class BlogsController < ApplicationController
def index
#blogs = Blog.all
end
def show
#blog = Blog.find(params[:id])
end
def new
#blog = Blog.new
end
def create
#blog = Blog.new
#blog.title = params[:blog][:title]
#blog.body = params[:blog][:body]
#blog.save
redirect_to blog_path(#blog)
end
def destroy
#blog = Blog.find(params[:id])
#blog.destroy
redirect_to blog_path(#blog)
end
def edit
#blog = Blog.find(params[:id])
end
def update
#blog = Blog.find(params[:id])
#blog.update(blog_params)
redirect_to blog_path(#blog)
end
end
def update
#blog = Blog.find(params[:id])
**#blog.update(blog_params)**
redirect_to blog_path(#blog)
end
here you are calling blog_params but you haven't defined it anywhere in your code.
See example here:
See strong params
You need to do this:
#app/controllers/blogs_controller.rb
class BlogsController < ApplicationController
def update
#blog = Blog.find params[:id]
#blog = #blog.update blog_params
end
private
def blog_params
params.require(:blog).permit(:title, :body) #-> in "permit" put the attributes of your "blog" model
end
end
The error is a standard programming issue - undeclared function.
Because you're starting - and to give you more context - the reason this is a problem is because you're calling blog_params when you run the .update method:
#blog.update blog_params
This, as mentioned by Pardeep Dhingra, is the strong params pattern introduced into Rails 4. Strong params prevents mass assignment by explicitly permitting particular attributes of your model.
Whilst your code is okay, you lack the strong params method (in your case blog_params), which Rails needs to complete the request. Adding the method will fix the issue.
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
I am a beginner in RoR (8 hours old) and was faced with a problem I can't get past. The tutorial presented in the get started guide on their site goes through setting a post entry example. I get the following error:
NoMethodError in Posts#show
Showing /Users/khalidalghamdi/blog/app/views/posts/show.html.erb where line #3 raised:
undefined method `title' for nil:NilClass
Extracted source (around line #3):
1
2
3
4
5
6
<p>
<strong>Title:</strong>
<%= #post.title %>
</p>
<p>
Rails.root: /Users/khalidalghamdi/blog
Application Trace | Framework Trace | Full Trace
app/views/posts/show.html.erb:3:in `_app_views_posts_show_html_erb__4427112910992919114_2164032300'
Request
Parameters:
{"id"=>"4"}
and this is the tutorial link http://guides.rubyonrails.org/getting_started.html and I am following section 5.6. It is not showing the posted details in the show.html.erb page. What am I doing wrong?
Update: Controller code:
class PostsController < ApplicationController
def new
end
def create
#post = Post.new(post_params)
#post.save
redirect_to #post
end
private
def post_params
params.require(:post).permit(:title, :text)
end
def show
#post = Post.find(params[:id])
end
end
Set the instance variable #post in PostsController#show action.
Currently, #post variable is set as nil so you are getting undefined method 'title' for nil:NilClass error.
Remove the show action from private. Its not getting called as you have made it private. Hence #post is not set.
For example: (As you haven't shared the code, I am giving an example)
class PostsController < ApplicationController
## ...
def show
#post = Post.find(params[:id])
end
## ...
private
def post_params
params.require(:post).permit(:title, :text)
end
end
Also, a better approach is to add a before_action in your controller where you can set the #post variable in order to avoid redundant code across multiple actions. This makes your code DRY too.
For example:
class PostsController < ApplicationController
## set_post would be called before show, edit, update and destroy action calls only
before_action :set_post, only: [:show, :edit, :update, :destroy]
## ...
def show
## No need to set #post here
end
## ..
private
def set_post
#post = Post.find(params[:id])
end
def post_params
params.require(:post).permit(:title, :text)
end
end
Seeing your controller code,you have defined your show action after the private method.
Put it above the private method like this
class PostsController < ApplicationController
def new
end
def create
#post = Post.new(post_params)
#post.save
redirect_to #post
end
def show
#post = Post.find(params[:id])
end
private
def post_params
params.require(:post).permit(:title, :text)
end
end
Note:
Any method defined after the private method is also treated as private.