How to make a class-based controller in Rails? - ruby-on-rails

I am trying to create a generic controller in Rails, to approach a similar functionality than the one offered in class-based views in Django. It would mean that just by making a subclass of this controller you would acquired all the methods generated by default with scaffold.
The code generated by scaffold in Rails is equal except for the name of the resource. Given an small example and based on the code generated by scaffold:
class ResourceController < ApplicationController
before_action :set_#{resource_under_scored}, only: [:show, :edit, :update, :destroy]
def index
##{resource_under_score_pluralized} = #{resource_model}.all
end
...
Given that definition I could write the following controller for resource big shoe:
class BigShoesController < ResourceController
end
Which would imply in the following code auto generated at runtime:
class BigShoesController < ApplicationController
before_action :set_big_shoe, only: [:show, :edit, :update, :destroy]
def index
#big_shoes = BigShoe.all
end
I still need to learn a lot of metaprogramming to achieve this but I though that there are more people who has wanted to acquired the same result. But I have not found the proper question.
How would you accomplish this ? I am looking for a way to implement the class, to see how it would be made to generated code based on variables. It is preferred this answer than a gem which will make the work.

It seems that you want InheritedResources: https://github.com/josevalim/inherited_resources.
It implements all the basic CRUD stuff. Example:
class ProjectsController < InheritedResources::Base
end
This controller has index, show, create, update and other methods, implemented in a standard manner. There are some possibilities for customization, see the readme file.

Related

Does before_action in class override actions defined in concern?

When using concerns in Rails controllers how do filters added via before_action operate? Specifically, if I have the following code for which actions in FeedsController will the before_action set_record run? All of them once (show, update, destroy, foobar), all of them once and twice before destroy or only before destroy, foobar (I presume authenticate still runs only before destroy)?
module JsonApiController
extend ActiveSupport::Concern
included do
before_action :authenticate, only %i[ destroy ]
before_action :set_record, only: %i[ show update destroy ]
end
def show/update/destroy
end
protected
def set_record
#record = controller_path.classify.constantize.find(params[:id])
end
end
class FeedsController < ApplicationController
include JsonApiController
before_action :set_record, only: %i[destroy, foobar]
def foobar
...
end
end
I'd like FeedsController to be able to add the set_record filter before any actions it wants without having to know what the JsonApiController did. So, ideally, the filter :set_record would execute once before each of show/update/destroy/foobar but I don't think that's what the code below accomplishes. But more than any particular solution I want to know how before_action works with concerns and included do so I minimize code duplication between concerns and classes.
If this was just inheritance then I know that before_actions are inherited. But this discussion suggests that a before_action in an included do in a module will be overwritten by one in the class but when I try to look at the source it suggests that append_callback is the default action so I'm confused.
From the Rails Guides: Action Controller Overview/Filters:
Calling the same filter multiple times with different options will not work, since the last filter definition will overwrite the previous ones.
That said if you want to change or update the configuration of a before_action then that new config will override all existing filters with the same method name.
Therefore, in your example, you will need to use the following new declaration to extend (actually override) the existing declaration from the included module:
before_action :set_record, only: %i[show update destroy foobar]

Why isn't an import needed for a class referred to by another class?

I'm starting the Rails tutorial by Michael Hartl and encountered something unclear to me almost immediately, coming from familiarity with JS/python/java/etc.
After using the scaffolding generator to create a Users resource, you have a controller that starts off like so in users_controller.rb:
class UsersController < ApplicationController
before_action :set_user, only: [:show, :edit, :update, :destroy]
# GET /users
# GET /users.json
def index
#users = User.all
end
It's referring to the User model in models/user.rb, I believe, which this:
class User < ApplicationRecord
end
Simple enough! The thing that I can't quite follow is how the controller is able to refer to User.all without importing the class. What mechanism is making User available to the other file?
Ruby does not have a module system. There is no such thing as import. Any code that is loaded in the current process is always available.

Share Controller Actions / Objects on Rails

As a Rails Rookie I wonder what is the proper way to reuse code that interact with database objects. For instance I have:
class PostsController < ApplicationController
before_action :set_post, only: [:show, :edit, :update, :destroy]
def index
#posts = Post.all
end
end
I also have a Welcome page with a Welcome controller:
class WelcomeController < ApplicationController
def index
#posts = Post.all
end
end
I would like to ALSO show the same list of posts on the Welcome page. I implemented a partial view for Posts and used it in Welcome view, but repeating the same database query on Welcome controller seems to be the wrong way to do it since I am repeating myself.
I also called the other controller action like this but I don't know if this is acceptable.
class WelcomeController < ApplicationController
def index
#posts = PostsController.new.index
end
end
I wonder if there is a right way (rails way) to share actions between controllers. In essence from WelcomeController call index action of PostsController so I don't have to implement repeat the method
Good question!
It might seem like too much repetition, but it is totally fine to repeat the database query in the controllers. Don't instantiate a new controller instance and then call the index method on it like you did.
Remember that the HTTP requests get routed to the right controller action and then render a view. So refactoring same views into partials and rendering those is perfectly fine, but don't bother about repeated code in the controllers, especially if it is just a simple query.
Once your app will grow, you will have more code in the controllers and it won't look like repetition so much.
If you have larger code blocks that are repeated, you can work with concerns (How to use concerns in Rails 4 they work for models, too).

before_filter works differently in development and production modes

I am working on a generic controller to provide default functionality to the RESTful actions, but I am having trouble when I need to add additional routes. The default behavior I am after renders all actions in either an index view, or a show view. My code is setup like this (lots of unrelated code is trimmed);
module RestController extend ActiveSupport::Concern
included do
before_action: setup_index_view,
only: [:index, :new, :create] << #additional_index_routes
before_action: setup_show_view,
only: [:show, :edit:, update, :destroy] << #additional_show_routes
end
def setup_rest_controller(_additional_index_routes,
_additional_show_routes)
#additional_index_routes = _additional_index_routes
#additional_show_routes = _additional_show_routes
end
def setup_index_view
# default behavior, sets #{collection_name} = {Model.collection}
# code trimmed
end
def setup_show_view
# default behavior, sets #{instance_name} = {Model.find(params[:id]}
# code trimmed
end
end
class Order < ApplicationController
def initialize
setup_rest_controller(
_additional_show_routes: [:quote, :contract, invoice_report])
end
def setup_index_view
#override default behavior to filter andpaginate collection
#orders = Orders.active.search(search_terms).paginate
end
#accept default behavior for setup_views
end
In development, this code is working fine (I admit that surprises me a little), but in production the additional routes are not running the setup_show_method. The only difference in the gem file is that development includes rspec-rails. Does anyone know why this code would behave differently?
EDIT
As soon as I hit post, the 'why' hit me. In development the code is reloaded at each request, and in production it is only loaded once...at first load #additional_show_routes is not set, and therefore no additional routes are added to the before_action call. New question then...how do I get the desired behavior?
Adding a call to before_action in the OrdersController overrides the one in RestController and breaks the default functionality.
Adding a call to before_action in setup_rest_controller throws a NoMethodError.
When you add a concern like this, is there a method like new that I can use instead of setup_rest_controller to set #additional_show_views earlier?
This feels like a hack, but it seems to get the desired result;
class OrdersController < ApplicationController
prepend_before_action Proc.new { send :setup_index_view},
only: [:new_restful_action]
def setup_index_view
#override default behavior to filter andp aginate collection
#orders = Orders.active.search(search_terms).paginate
end
#accept default behavior for setup_views
end
I also drop all references to #additional_{X}_routes in RestController. I don't know that prepend_ is required in the code as written above, but in my actual code there are other before_filters that need to run after #additional_{X}_routes...

Rails Variable across all controller actions

This should be a very simple rails question. I have a variable like the following.
#administration = Administration.first
I want this variable to be accessible through every controller action across all my controllers so for example if I have a Product controller and inside of it I have the usual CRUD actions, I want the #administration variable as defined above to be put into all the CRUD actions. (It would not be needed in destroy or create or update). I have many controllers throughout my project and I was wondering if there is an easier way than adding it manually through all of the actions that I want it in.
I tried a global variable
$administration = Administration.first
but I run into an issue where it is not updated when I update the actual content of the Administration.first table. Also, I would like to avoid global variables.
Any help would be much appreciated. Thanks! :)
You could add a before_filter to your ApplicationController that sets the administration variable before any action is called and you can limit it to only the actions you require.
class ApplicationController < ActionController::Base
...
before_filter :set_admin
def set_admin
#administration = Administration.first
end
..
http://api.rubyonrails.org/v2.3.8/classes/ActionController/Filters/ClassMethods.html
Just extending Christos post...
If you don't want #administration to be accessible to destroy, create and update controller actions then add :except => :action to before_filter like this:
before_filter :set_admin, :except => [:create, :update, :destroy]
On Rails 4 and 5 before_filter it's deprecated. You can use this instead:
before_action :set_admin, except: [:create, :update, :destroy]

Resources