There are already threads on how to pass parameters between methods of the same controller.
But is there a way of passing parameters between methods of different controllers ?
Because sometimes you have for instance an article for which, when you call the #update action on it, you also wish at the same time to update the tags associated with it over the #update action of the TagsController.
The idiom would be something like to instantiate TagsController in ArticlesController#actions and then pass a new instance of Rack::Response with only a :tags part of the whole params-hash to it while doing tags_controller_instance.send(:update).
You would have to send only special parts of the params-hash which the ArticlesController received because TagsController will have different StrongParameters!
I think it boils down to the question about how to create a Rack::Request that copies the Request of ArticlesController on one hand and how to pass to it not the whole params-hash but only meaningful parts of it.
Then it should be possible to ask for the updated taglist afterwards with this.tags in ArticlesController#update, right?
Thanks
Von Spotz
The idiom would be something like to instantiate TagsController in ArticlesController#actions and then pass a new instance of Rack::Response with only a :tags part of the whole params-hash to it while doing tags_controller_instance.send(:update).
Please don't do that! It will be hard to understand & maintain. There might be other side effects you haven't even thought about too.
The question is, what does the TagsController#update do that you don't want to replicate in the ArticlesController? If it's some complex logic, I think you should abstract this e.g. in a (service) object and call it instead.
Something like this:
class UpdateTags
def self.run(params)
new(params).run
end
def initialize(params)
#params = params
end
def run
# copy logic from TagsController
end
end
and then you can use / reuse this service in your controllers
class TagsController
def update
UpdateTags.run(params)
end
end
class ArticlesController
def update
# update Article or move this in a dedicated service too
UpdateTags.run(params)
end
end
Another approach could be to let Article accept attributes for Tags with nested attributes.
Edit
To elaborate a little bit why instantiating another controller is not a good idea.
What about before filters? Is it fine to execute them (again)?
What about view rendering? You obviously don't want to render views so that's additional work and might have side effects?
Other side effects like caching, logging, data analysis.
Instantiating a controller is not a public API so this can change between Rails versions making an update difficult.
It's not a common pattern so it will be hard to understand
I would just stress again that this is not a good idea to do. Duplicated code is better than the wrong abstraction.
This just sounds like a crazy Wile E. Coyote solution to a problem that's trivial to solve with nested attributes.
The only public methods of a controller in Rails should be the actions of the controller which are the methods that respond to HTTP requests and these should only be called though http calls.
Any other method should be private/protected. There is no valid scenario where you would be calling TagsController#update off another controller to update tags as that method should do only one thing - update tags in response to PATCH /tags/:id.
If you want to update an article and its tags in one single request use accepts_nested_attributes_for :tags:
class Article < ApplicationRecord
has_many :tags
accepts_nested_attibutes_for :tags
end
That creates a tags_attributes= setter that you can use update the nested records together with the parent.
And if you want to share functionality between classes where classical inheritance is not suitable use horizontal inheritance. In Ruby this means modules:
module Taggable
private
def tag_attributes
[:foo, :bar, :baz]
end
end
class TagsController < ApplicationController
include Taggable
# PATCH /tags/:id
def update
#tag = Tag.find(params[:id])
if #tag.update(tag_params)
redirect_to #tag, success: 'Tag updated'
else
render :edit
end
end
private
def tag_params
params.require(:tag).permit(*tag_attibutes)
end
end
class ArticlesController < ApplicationController
include Taggable
def update
if #article.update(article_params)
redirect_to #article, success: 'Article updated'
else
render :edit
end
end
private
def article_params
params.require(:article).permit(:title, :body, tags_attributes: tag_attibutes)
end
end
<%= form_with(model: #article) do |f| %>
...
<%= f.fields_for :tags do |tag| %>
<div class="field">
<%= tag.label :foo %>
<%= tag.text_field :foo %>
</div>
<% end %>
<% f.submit %>
<% end %>
In some cases you might choose to use AJAX instead to create/update/delete a nested resource "on-the-fly" by sending asynchronous HTTP requests which do call another controller (but not in the same request). However this is really out of scope for this question.
If you're using params in many controllers, just add them to private methods in the application controller.
Related
I have the following associations defined in my application:
class Person
belongs_to :type
end
class Type
has_many :people
end
Now, in the new and edit form for person, I need to give a dropdown that will show all the types which will come from the Type model.
Now there are two approach to do the same:
1. make an instance variable in controller for types and access that in the form view.
class PeopleController
before_action :get_types, only: [:new, :create, :edit, :update]
def new
end
def create
end
def edit
end
def update
end
private
def get_types
#types = Type.all
end
end
person/_form.html.erb
...
<%= f.select :type_id, #types.collect{ |type| [type.name, type.id]} %>
...
2. Making a database query in the person_helper
person_helper.rb
module PersonHelper
def get_types
Type.all
end
end
person/_form.html.erb
...
<%= f.select :type_id, get_types.collect{ |type| [type.name, type.id]} %>
...
So, I want to know which is the better approach and why.
Note: As per MVC paradigm, controller will provide the necessary data to the views. Neither I am not able to find any difference in the query execution in both the cases nor, I am able to find any good explanation regarding the same apart from the MVC part.
The idea with the helper is reuse functionalities for example formatting something or extract complicated logic.
So, if that query is something that you will need in many cases, would be a good idea have it in the helper, there is not really an difference in the query execution, is more about use and have a code more clean.
Trying to figure our how to set up associations in form.
I have 3 models:
class Request < ActiveRecord::Base
has many :answers
has many :users, through: :answers
end
class Answer < ActiveRecord::Base
belongs to :user
belongs to :request
end
class User < ActiveRecord::Base
has many :answers
has many :requests, through: :answers
end
I am trying to figure out: how to have a User link to Answer#new from Request#Show, and then create an Answer record passing in the Request#Show request_id from the previous page - creating an association between the User's Answer and the Request he was viewing.
My method of doing this now is: I flash the request_id value on Request#Show, and then when a User links to Answer#new, it passes the flashed value into a hidden form tag on Answer#new. This does not seem like the best way to do this.
Any thoughts?
Kudos for the creative approach using flash, however your right there is an easy way. You can pass parameters much between controllers just like passing parameters between methods using the route names.
I didn't quite follow what it was you were trying to achieve in this case but it looks like this blog entry here should get you started..
https://agilewarrior.wordpress.com/2013/08/31/how-to-pass-parameters-as-part-of-the-url-in-rails/
Good luck!
User link to Answer#new from Request#Show
This can be achieved with either sessions or nested resources (or both!). Let me explain:
I would definitely add a nested resource to your requests routes:
#config/routes.rb
resources :requests do
resources :answers, only: [:new, :create] #-> url.com/requests/:request_id/answers [POST]
end
This gives you the ability to call a "nested" route (IE one which sends data to a child controller, and requires "parent" data to be appended to the request).
In your case, you want to create an answer for a request. The most efficient way is to use a routing structure as above; this will allow you to use the following controller method:
#app/controllers/answers_controller.rb
class AnswersController < ApplicationController
def new
#request = Request.find params[:request_id]
#answer = #request.answers.new
end
def create
#request = Request.find params[:request_id]
#answer = #request.answers.new answer_params
#answer.save
end
private
def answer_params
params.require(:answer).permit(:title, :body)
end
end
The above gives you the ability to create an answer by passing the request_id to the nested route. You must note the corresponding route will require a POST method in order to work.
You don't need the new method. If you wanted it, it can easily be handled with the above structure.
Passing the user is a little more tricky.
You can either use the routes, or set a session.
I would personally set a session (it's cleaner):
#app/controllers/requests_controller.rb
class RequestsController < ApplicationController
def show
session[:user_id] = #user.id #-> I don't know how you populate #user
end
end
This will give you the ability to access this session here:
#app/controllers/answers_controller.rb
class AnswersController < ApplicationController
def new
user = User.find session[:user_id]
end
end
#app/views/requests/show.html.erb
<%= link_to "New Answer", request_new_answer_path(request) %>
--
If you're using Devise, the user object should be available in the current_user object (which means you don't have to set session[:user_id]):
#app/controllers/answers_controller.rb
class AnswersController < ApplicationController
def new
## current_user available here if using devise
end
end
To assign a #user to the new answer record, just do this in answers#create:
#app/controllers/answers_controller.rb
class AnswersController < ApplicationController
...
def create
#request = Request.find params[:request_id]
#answer = #request.answers.new answer_params
#answer.user = current_user
#answer.save
end
end
Something like this worked for me:
I have two models (Formula and FormulaMaterial)
Formula has_many FormulaMaterials, which belongs to Formula
My Formula controller sets #formula like so:
#formula = Formula.find(params[:id])
I list my Formula Materials in my Formula show.html.erb by declaring it in my Formula controller like so:
#formula_materials = FormulaMaterial.where(:formula_id => #formula)
When I want to add a new FormulaMaterial to my Formula, the "New Formula Material" button in my show.html.erb file looks like this:
<%= link_to 'Add Material To Formula', new_formula_material_path(:formula_id => #formula), class: "btn btn-success" %>
In the "new_..._path" I set the associated id to the #formula variable. When it passes through to the new.html.erb for my FormulaMaterial, my URL looks like so:
http://localhost:3000/formula_materials/new?formula_id=2
In my FormulaMaterial new.html.erb file, I created a hidden_field that sets the value of the association by using "params" to access the formula_id in the URL like so:
params[:formula_id] %>
I am not sure if this is the best way to do this, but this way has allowed me to pass through the view id from the previous page as a hidden, associated and set field in the form every time.
Hope this helps!
I'm making an online magazine style website and am having difficulties getting the syntax right for my final part of the project. The relationships are working as they should I am just having trouble calling the intended records.
Each post belongs to a category with category_id being the foreign key. When a user clicks this link, <%= link_to 'News', categories_path(:category_id => 1) %>, I'd like for them to be brought to an index page showing only posts with a category_id matching the parameter in the URL.
I've been messing around in the categories_controller.rb for almost two hours now with no luck. Anyone be so kind as to throw this noob a bone?
There are a few components of what you're trying to do. We'll start with the routing side, and make our way to the controller.
First, you need to make the proper routes. Since the post belongs to a category, you will need to have the category id in order to handle performing any sort of operations on the posts. So we'd need a route like /category/:category_id/posts/:id. Luckily, Rails has something to handle this. If you nest a resources within a resources, it'll generate these routes. So, we end up with this:
resources :categories do
resources :posts
end
And that will get you what you want in terms of routes. But now we have to actually implement it. So, we're going to need to take a look at the controllers. If you notice, all of those routes have a :category_id - so looking up the category shouldn't be too difficult:
class PostsController < ApplicationController
before_action :load_category
private
def load_category
#category = Category.find(params[:category_id])
end
end
Now, you have the category loaded, and it shouldn't be too difficult to implement the other methods from there:
class PostsController < ApplicationController
before_action :load_category
def index
#posts = #category.posts
end
def show
#post = #category.posts.find(id: params[:id])
end
# ...
end
In order to reference the Post index path, you'll have to use category_posts_path helper.
Your problem is that you're trying to use an existing route to handle some new functionality (for which it was incidentally not designed). That categories_path route is meant to take you to your category index.
You need to create a method in your controller to perform the functionality you want to see.
class PostsController < ApplicationController
...
def posts_by_category
#posts_by_category = Post.where("category_id = ?", params[:category_id])
end
...
end
Then you're going to need a view to display your #posts_by_category array (I'll leave this exercise to you).
And now for the key to your problem: you need a route pointing to the posts_by_category method.
get 'posts/posts_by_category' => 'posts#posts_by_category'
Now you should be able to create your link with the correct route:
<%= link_to 'News', posts_by_category_path(:category_id => 1) %>
Newbie question
I'm building an employee form, and one of the field in the form is a choice list where the data comes from department.
ActiveRecords: Employee and Department.
I've to pass both the employee and department active records from the controller to the view. How do I pass these multiple active records to the view to create an employee which uses department to build the choice list?
The common Rails approach is just to set the instance variables in your controller action:
Approach 1:
class EmployeesController < ApplicationController
def new
#departments = Department.all
#employee = Employee.new
end
end
And in your views:
<%= form_for(#employee) do |f| %>
...
<% end %>
Rails does some magic behind the scenes to expose the controller's instance variables to the view template.
A lot of poeple (myself included) feel that allowing a view template object to access the
controller's instance variables directly is a violation of object-oriented programming. An object's instance variables should only be accessible to that instance.
Approach 2:
The better approach is to wrap these variables in getter methods which are then defined as helper methods:
class EmployeesController < ApplicationController
# Make these methods available to the view.
helper_method :departments, :employee
def new
end
# Private - as they're not actions
private
def departments
#departments = Department.all
end
def employee
#employee = Employee.new
end
end
And in your views:
<!-- NOTE - no # sign, it's not an instance variable -->
<%= form_for(employee) do |f| %>
...
<% end %>
In this case, you're defining a public API for the EmployeeController with two methods, departments and employee, which may be accessed.
Practically, this achieves the first thing as the first example; the difference is mostly academic.
Approach 3:
For complex views that need to access a few different variables defined in the controller, you could design your own presenter (or facade) object.
This is probably overkill for your employee form, but to use that as an example:
class EmployeesController < ApplicationController
# Make these methods available to the view.
helper_method :employee_form_presenter
def new
end
# Private - as they're not actions
private
def employee_form_presenter
#employee_form_presenter = EmployeeFormPresenter.new(params) # you can pass args from the controller if you need them
end
end
Create a presenter:
# in app/presenters/employee_form_presenter.rb
class EmployeeFormPresenter
attr_reader :employee, :departments
def initialize(eployee_atts={})
#employee = Employee.new(eployee_atts)
#departments = Department.all
end
end
And in your view:
<%= form_for(employee_form_presenter.employee) do |f| %>
...
<% for dept in employee_form_presenter.departments do %>
...
<% end %>
<% end %>
This keeps your controller really clean and simple - making it easier to test and add extra behaviour to.
All of these approaches work. A presenter is only really required when your controller starts to get more complex. But personally, I
avoid calling a controller's instance variables from the view as a matter of best-practice.
Say your view name is employee_form.html.erb then in your controller there should be a method with same name(not necessary).
def employee_form
#employee = Employee.first
#department = Department.first
end
then you can access these instance variables on your employee_form.html.erb view like
<%= #employee.name %>
<%= #department.name %>
etc.
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