Reputation:
I'm trying to build a profile page that displays posts sent only to the requested user, and allows the visitor to write a post of their own. Because this simplified example should have two distinct controllers: users and posts, I made partials for each post action to render within the user's show action.
Directory structure for my views directory looks like this:
- posts
- _index.html.erb
- _new.html.erb
- users
- show.html.erb
... (etc.)
Section that displays these partials within the user's show.html.erb
:
<section>
<h3>Posts:</h3>
<%= render '/posts/new', :post => Post.new %>
<%= render '/posts/index', :posts => Post.where(target_id: params[:id]) %>
</section>
I eventually found out that you could pass variables into the partial in this render
line, and though this works, it's very messy and probably doesn't follow the best practices.
Ideally, I'd want these partials to be connected with the posts controller so I can write more complex database queries in a place that isn't the view:
class PostsController < ApplicationController
def new
@post = Post.new
end
def index
@posts = Post.where(target_id: params[:id])
end
def create
@post = Post.new(post_params)
@post.user_id = current_user.id
@post.target_id = params[:post][:target_id]
if @post.save
redirect_to :back, notice: 'You published a post!'
else
render new
end
end
private
def post_params
params.require(:post).permit(:body)
end
end
Currently, I haven't found a way of doing this. I know this is a newb question, but thanks for any help in advance.
Upvotes: 1
Views: 1425
Reputation: 5120
I agree somewhat with @Mori's advice. As he said, you are trying to put too much logic into the controller. I think this was a result of you trying to get it out of the view, which is the right idea, but you want business logic to be in the model.
Also, those index
and new
actions for PostsController
are never going to be called. When you are calling the render posts/new
for example, that is rendering the view, not the controller action. So, those controller actions have no reason to exist.
I would implement the fix in perhaps a different way than Mori described. It's a recommended practice to try and pass as few instance variables from the controller to the view as possible (see 3rd bullet in the linked section).
Since it's really the show
action of the UsersController
we are talking about here, I as someone trying to understand your code would assume the instance variable you are passing to the show
view is something like @user
.
You may want to use an includes
method when instantiating the @user
object. The includes
statement will allow you to load the additional models you will need to instantiate using the minimum number of queries possible (preventing an N+1 query situation). You probably don't want to load every single one if there are thousands of matching posts, so I put an arbitrary limit of 10 on that.
UsersController
def show
@user = User.find(params[:id]).includes(:received_posts).limit(10)
end
#....
View
<section>
<h3>Posts:</h3>
<% unless @user.id == current_user.id %>
<%= render 'posts/form', post: Post.new(user_id: @user.id) %>
<% end %>
<%= render @user.received_posts %>
</section>
Putting the partial for a new post
instead as a view called posts/form
will allow you to reuse that form if you want to render an edit action (form_for
knows which action to use on submit by calling the passed model's persisted?
method).
Note that this code assumes the User
model has the second relationship with posts set up to be called received_posts
, but you can change it to whatever reflects the reality. By passing the received_posts
collection to the render method, Rails is smart enough to know that if you want to render a collection of Post
models to look for a posts/_post
partial and render one for each Post
. It's a little cleaner looking IMO. Just make sure to move your posts/show
code into that. posts/show
implies this is its own action and not something used as a partial for something else.
Upvotes: 0
Reputation: 27779
You are attempting to treat your controllers like models: doing the post work in post controller and the user work in user controller. But controllers are task-oriented, not model-oriented.
Since you want posts info in your user form, it's typical to gather it in the user controller. E.g.
class UsersController < ApplicationController
def show
...
@posts = Post.where(user_id: user.id)
end
end
That @posts
instance variable is visible in the show template and any partials it calls. But many coders prefer to send it explicitly through render
arguments, as more functional:
<%= render '/posts/post_list', posts: @posts %>
For one thing it's easier to refactor when you can see at a glance all of the partial's dependencies.
Upvotes: 1