rails controller design : listing and create on the same view - ruby-on-rails

starting from the rails blog tutorial, i want to have listing and create functionality on a single view. But i don't known how to design the controller to accomplish this.
The index view must show a simple list of posts and a form to create a new post.
Can i solve this with partials? How? I need a "new" and "create" methods? With only create is not enough?
class MyPostsController < ApplicationController
def index
#posts = Post.all
end
def new
end
def create
end
end

If you want to have the form in the index view, render the form. I'd recommend a partial, but it's not mandatory. Depending on the form implementation you may need a new Post model, that's as easy as putting a #post = Post.new in the index action.
The reason create may not be "enough" is because some forms are "for" an instance of the model. In those cases generally the new action makes a new Post and renders the form, whereas the create action actually saves (creates) it.

Related

Having multiple instance variables in rails controller action? (Rails best practices)

Say for example I have two models, posts and category. Now say I want to make it so the from the category show page you can create a new post using the form_for method. To do this, you will obviously need access to the #category variable and a new instance of a post (#post). Is this acceptable code in the controller?
#app/controllers/categories_controller.rb
def show
#category = Category.find(params[:id])
#post = Post.new
end
Or is it bad practice to have two instance variables defined in the one controller action - and if it is, what would be the best practice for a case like this?
I usually do something like:
#app/controllers/categories_controller.rb
helper_method :category
helper_method :post
def show
end
private
def category
#_category ||= params[:id] ? Category.find(params[:id]) : Category.new(params[:category])
end
def post
#_post ||= Post.new(params[:post])
end
Then, in your views, just refer to post or category (not #post or #_post). The nice thing is you can remove the same logic from your new, delete, etc methods...
Actions related to posts should be in the PostsController as much as possible.
Let's say the user is looking at all posts under the category "rails": /categories/rails
There's a button on that page to create a new post under the "rails" category, href: /posts/new?category=rails
This takes you to PostsController#new where you instantiate a new Post, validate the category param and build a view. This view could either be a new page, or a modal popping up.

Render page more flexible

in my current project, have a assumption that a lot of different UI will be required.
ex> there will be two clients, boogle and yumhoo, these two client want totally different view.
I made a prototype like this,
class HomeController < ApplicationController
after_action :render_ui
def index
end
def render_ui
render "#{self.controller_name}/#{ENV['CLIENT_UI']}/#{self.action_name}"
end
end
my plan was to generate the path to the view file dynamically,
but there is a big problem here,
as you all know rails automatically runs,
render 'there own contoller and action name combination'
and throws an error,
Render and/or redirect were called multiple times in this action
Is there a way to skip the default automatic render feature in rails?
after_action is called when the view has already been rendered. If you want to render a specific file, simply call render in the action itself.
class HomeController < ApplicationController
def index
render "#{self.controller_name}/#{ENV['CLIENT_UI']}/#{self.action_name}"
end
end
You may also want to consider to use the Rails 4.1 template variants feature.

Where to declare object with shared partial form in Rails?

I have a text_area form to create a quick Idea object. This form appears on many parts of the site so I move it as a shared/_idea_form.html.haml file.
I'm currently defining the new object in every controller action where the form is present with #ideas = Idea.new to make it work.
Is there a more convenient/global way define the #ideas = Idea.new outside each action? I.e. as a before_filter in the application controller. I'm not sure if that would be the right approach to this.
you can put it directly in view
<%= form_for #idea ||= Idea.new do |f| %>
#your text area
<%end%>
If you have it on most of the actions yes, that should be a good way. If was me in your place I think I would brake the rules and would do that in the partial... Sometimes rules dosen't make sense and this time is one of it. You just want a form on every page, and so you need to create always a new Idea for the form.. Or do that on the partial or just create the form without helpers.
Just one opinion :)
There are tons of options: using the decent_exposure gem (try it, it's cool!), using before_filters for setting the value of the #idea, manually creating new Idea in your form, defining some helper which will provide your form with a prepared Idea.
in app/controllers/application_controller.rb:
class ApplicationController < ActionController::Base
before_filter :create_new_idea
def create_new_idea
#idea = Idea.new
end
end
That will set #idea to a new object on every request. There must be some cases where you don't want #idea set to a new instance or even set at all. In that case there are a number of options, here's one:
class ApplicationController < ActionController::Base
before_filter :create_new_idea, lambda {|controller| controller.set_new_idea?}
def create_new_idea
#idea = Idea.new
end
def set_new_idea?
# this should be false in some case
end
end

Rails: call another controller action from a controller

I need to call the create action in controller A, from controller B.
The reason is that I need to redirect differently when I'm calling from controller B.
Can it be done in Rails?
To use one controller from another, do this:
def action_that_calls_one_from_another_controller
controller_you_want = ControllerYouWant.new
controller_you_want.request = request
controller_you_want.response = response
controller_you_want.action_you_want
end
You can use a redirect to that action :
redirect_to your_controller_action_url
More on : Rails Guide
To just render the new action :
redirect_to your_controller_action_url and return
The logic you present is not MVC, then not Rails, compatible.
A controller renders a view or redirect
A method executes code
From these considerations, I advise you to create methods in your controller and call them from your action.
Example:
def index
get_variable
end
private
def get_variable
#var = Var.all
end
That said you can do exactly the same through different controllers and summon a method from controller A while you are in controller B.
Vocabulary is extremely important that's why I insist much.
You can use url_for to get the URL for a controller and action and then use redirect_to to go to that URL.
redirect_to url_for(:controller => :controller_name, :action => :action_name)
This is bad practice to call another controller action.
You should
duplicate this action in your controller B, or
wrap it as a model method, that will be shared to all controllers, or
you can extend this action in controller A.
My opinion:
First approach is not DRY but it is still better than calling for another action.
Second approach is good and flexible.
Third approach is what I used to do often. So I'll show little example.
def create
#my_obj = MyModel.new(params[:my_model])
if #my_obj.save
redirect_to params[:redirect_to] || some_default_path
end
end
So you can send to this action redirect_to param, which can be any path you want.
Perhaps the logic could be extracted into a helper? helpers are available to all classes and don't transfer control. You could check within it, perhaps for controller name, to see how it was called.
Composition to the rescue!
Given the reason, rather than invoking actions across controllers one should design controllers to seperate shared and custom parts of the code. This will help to avoid both - code duplication and breaking MVC pattern.
Although that can be done in a number of ways, using concerns (composition) is a good practice.
# controllers/a_controller.rb
class AController < ApplicationController
include Createable
private def redirect_url
'one/url'
end
end
# controllers/b_controller.rb
class BController < ApplicationController
include Createable
private def redirect_url
'another/url'
end
end
# controllers/concerns/createable.rb
module Createable
def create
do_usefull_things
redirect_to redirect_url
end
end
Hope that helps.
You can call another action inside a action as follows:
redirect_to action: 'action_name'
class MyController < ApplicationController
def action1
redirect_to action: 'action2'
end
def action2
end
end
Separate these functions from controllers and put them into model file. Then include the model file in your controller.

Rendering different controller actions in Rails when using resource-oriented controllers

Say I'm making a Q&A site like StackOverflow. I have two resources: Question and Answer. I'm using default Rails RESTful resource routes, so each resource has its own controller and methods for creating it.
In the /questions/show view, I want to allow the user to submit an answer for the particular question. The form will POST to /answers, which will get routed as a request to the AnswersController with a call to the create method.
If the answer was created, I can simply redirect back to the original question. However, I'm running into trouble dealing with validation failures on the answer object. I need to render the /question/show view and show the validation errors for the answer object. It's not clear to me how to best do this.
Here are example snippets of what the two controllers might look like.
class AnswersController < ApplicationController
def create
#answer = Answer.new(params[:answer])
if #answer.save
redirect_to #answer.question
else
# What should go here??
end
end
end
class QuestionsController < ApplicationController
def show
#question = Question.find(params[:id])
#answer = Answer.new(:question_id => #question.id)
end
end
What should go in the else clause of the AnswersController's create method? A redirect seems wrong, since the error is really caused by the same request. Calling something like render :template => 'questions/show' seems wrong too, since I have to initialize any instance variables that the template depends on.
This style of having separate actions for calling GET to view the form for creating an object and calling POST to actually create the object seems to work well within a single controller.
How can it be done across controllers?
Try this on for size. It redirects, but passes back the dodgy answer object full of errors.
class AnswersController < ApplicationController
def create
#answer = Answer.new(params[:answer])
# stash the dodgy answer if it failed to save
session[:answer] = #answer unless #answer.save
redirect_to #answer.question
end
end
class QuestionsController < ApplicationController
def show
#question = Question.find(params[:id])
# if we have one stashed in the session - grab it from there
# because it probably contains errors
#answer = session[:answer] || Answer.new(:question_id => #question.id)
end
end
Some details need adding (eg clearing it from the session when done) etc

Resources