To have a cleaner code I want to split my controller in some concerns.
In my routes.rb how to redirect to concern without redefine the methods of concern index show destroy create ...
class SomeController
include SomeConcern
def index
end
end
module SomeConcern
def index
end
end
Sorry for my bad english.
Lets say we have a CarsController and AirplanesController that have the typical create and new actions.
class AirplanesController < ApplicationController
def new
#airplane = Airplane.new
end
def create
#airplane = Airplane.new(create_params)
if #airplane.save
redirect_to #airplane
else
render :new
end
end
# ...
end
class CarsController < ApplicationController
def new
#car = Car.new
end
def create
#car = Car.new(create_params)
if #car.save
redirect_to #car
else
render :new
end
end
# ...
end
To dry this up we can extract the shared code to a module:
module Createable
extend ActiveSupport::Concern
included do
attr_accessor :resource
alias_attribute :self.controller_name.to_sym, :resource
end
def new
#resource = resource_class.new
yield #resource if block_given?
end
def create
#resource = resource_class.new(create_params)
if #resource.save
yield #resource if block_given?
redirect_to #resource
else
render :new
end
end
private
def create_params
raise "not implemented controller!"
end
def resource_class
#resource_class ||= self.controller_name.classify.constantize
end
end
We can then apply it to the controller classes by:
class CarsController < ApplicationController
include Createable
def create_params
params.require(:car)
.permit(:model) # ...
end
end
class AirplanesController < ApplicationController
include Createable
def create_params
params.require(:airplane)
.permit(:model) # ...
end
end
But a very important point here is that you are not routing to the module. The module is providing methods to the controller class.
You have to always map to your controller. Concerns are modules where you can put shared logic (it makes sense only in case you need 2 absolutely similar methods in 2 different controllers).
I think, that such code should work:
class SomeController
include SomeConcern
end
module SomeConcern
def index
end
end
Isn't it?
But concerns mostly used to move out some private helper methods from controller, rather actions as we do in this code piece
Related
Doing API for my first Rails project.
I have base class ApiController for all the APIs:
module Api
class ApiController < ::ApplicationController
respond_to :json
skip_before_action :verify_authenticity_token
def index
#collection = resource_class.all
render json: #collection.as_json(as_json_collection)
end
private
def resource_class
raise NotImplementedError
end
def as_json_collection
{}
end
end
end
And I have child class UsersController:
module Api
class UsersController < ApiController
private
def resource_class
User
end
def resource_params
params.require(:user).permit(:name, :email)
end
end
end
My routes:
namespace :api do
resources :users
end
Then I'm going to my_app/api/users I have error:
The action 'index' could not be found for Api::UsersController
But then I changing UsersController writing it's own index class, everything works fine and I'm having all my Users in JSON format.
I've alrady tried to comment all private marks in both classes, but that doesn't help.
I don't want to write an API for every entity in my project and I'd like to avoid this problem in future.
I got it to work with this:
module Api
class ApiController < ::ApplicationController
def index
respond_to do |format|
format.json { render json: '"abc"' }
end
end
end
end
module Api
class UsersController < ApiController
end
end
The URL was http://localhost:3000/api/users.json
So for you I suggest:
module Api
class ApiController < ::ApplicationController
def index
respond_to do |format|
format.json do
#collection = resource_class.all
render json: #collection.as_json(as_json_collection)
end
end
end
end
end
module Api
class UsersController < ApiController
def resource_class
User
end
def resource_params
params.require(:user).permit(:name, :email)
end
end
end
Its supposed to be like this:
class Api::ApiController < ApplicationController
and do not forget to remove extra end, end of the file!
#sample
- api(folder)
-- api_controller.rb (Api::ApiController < ApplicationController)
-- users_controller.rb (Api::UsersController < Api::ApiController)
application_controller.rb
You need to read this my friend:
rails routes
When you do this:
namespace :api do
resources :users
end
rails creates CRUD routes automatically which means that my_app/api/users will translate to: .../users#index.
Do this to see your routes created by rails:
rails routes and for specific word (e.g. user): rails routes | grep user
Seeing is believing ;)
I have a controller that looks something like this:
module Guardians
class StudentsController < ApplicationController
def show
#student = Student.find params[:id]
authorize #student, policy_class: StudentPolicy
end
end
end
Because the controller is within a module, the policy class which is used is Guardians::StudentPolicy, which is what I want.
However I now have another controller:
module Teachers
class StudentsController < ApplicationController
def show
#student = Student.find params[:id]
authorize #student, policy_class: StudentPolicy
end
end
end
Here the policy_class used is Teachers::StudentPolicy
But because the show method itself is identical, ideally I would like to dry this up with a concern. However if I cannot seem to do this, as
authorize #student, policy_class: StudentPolicy
will no longer automatically call the namespaced policy class when it is called from inside the concern.
What is the DRYest way to achieve this?
If you create a concern like this:
module StudentsConcern
extend ActiveSupport::Concern
included do
before_action :find_and_authorize_student, only:[:show]
end
private
def find_student_and_authorize
#student = Student.find(params[:id])
if self.class.name.deconstantize == 'Teachers'
authorize #student, policy_class: Teachers::StudentPolicy
else
authorize #student, policy_class: Guardians::StudentPolicy
end
end
end
And then include this in both of the controllers and clear the show actions.
I'm creating a controller in Rails, and I'm looking for ways to have different strong parameters for different controller methods
In update and new actions, I would want to require post
params.require(:post).permit(:body, :is_public, :title, :id)
But in post/index, i don't need to require these parameters.
How do you make different requirements strong parameters for different controller methods?
Your "strong parameters methods" are just Ruby methods. You can have however many you want.
class PostsController < ApplicationController
def create
#post = Post.new(create_params)
end
def update
#post = Post.find(params[:id])
if #post.update(update_params)
# ...
else
# ...
end
end
private
def base_params
params.require(:post)
end
# Don't take IDs from the user for assignment!
def update_params
base_params.permit(:body, :title)
end
def create_params
base_params.permit(:body, :title, :foo, :bar)
end
end
You can also name them whatever you want. Calling it [resource_name]_params is just a scaffolding convention.
Just do something like
class FooController < ApplicationController
def create
#post = Post.new(create_params)
if #post.save
blah
else
blah
end
end
def index
... something else
end
private
def create_params
params.require(:post).permit(:body, :is_public, :title, :id)
end
end
To keep to restfull protocol, I need to do /api/backup_jobs/777/errors.
In rails, the parent controller- I have:
module Api
class BackupJobsController < ApplicationController
respond_to :json
def show
#backup_job = #backup_jobs.find(params[:id])
respond_with data: #backup_job
end
end
end
in the child controller:
module Api
class ErrorsController < BackupJobsController
respond_to :json
def index
respond_with data: #backup_jobs.find(params[:id]).backup_events.errors
end
end
end
But obvisouley this isn't going to work because params[] doesn't exist for /api/backup_jobs/777/errors
How can I pass the #backup_job = #backup_jobs.find(params[:id]) from the parent controller's def show to the child controller and have it accessible in the child's def index?
You cannot do that because when an ErrorsController is created and used, you will not have a BackupsJobsController that ran before it.
This comes down to the nature of HTTP being a request-response protocol.
Instead, you can extract the line of code you wrote into a method that will be inherited by the ErrorsController.
backup_jobs_controller.rb:
module Api
class BackupJobsController < ApplicationController
def show
find_backup_job
respond_with data: #backup_job
end
protected
def find_backup_job
#backup_job = #backup_jobs.find(params[:id])
# or maybe #backup_job = BackupJob.find(params[:id])
end
end
end
errors_controller.rb:
module Api
class ErrorsController < BackupJobsController
respond_to :json
def index
respond_with data: find_backup_job.backup_events.errors
end
protected
def find_backup_job
#backup_job = BackupJob.find(params[:backup_job_id])
end
end
end
I have made the following validation method:
def if_admin(&block)
if #current_user.administrator?
yield
else
redirect_to '/go_away'
end
end
and i find my classes are increasingly looking like:
class Foo < ApplicationsController
def index
if_admin do
.....
end
end
def show
if_admin do
.....
end
end
def new
if_admin do
.....
end
end
def edit
if_admin do
.....
end
end
.......
end
I want to know if there is anything similar to before_action which would pass the method into the if_admin method, thus DRYing up the code?
Just like you wrote, there is before_action. You can use it like this:
class Foo < ApplicationsController
before_action :if_admin
# ...
private
def if_admin
redirect_to '/go/away' unless #current_user.administrator?
end
end