Can you have non-restful methods in a controller which includes the WickedWizard gem?
Controller:
class Books::BookUpdateController < ApplicationController
include Wicked::Wizard
steps :title_step, :ai_archive_step, :ai_override_step #etc
def show
...
end
def update
...
end
def waterfall
...# loads of code to set up instance variables in the view, which I don't want to have to include in the normal show action for all the wizard steps.
end
end
Routes:
resources :book_update do
member do
get 'waterfall'
... and others
end
end
Version 1 and lower of the gem allows non restful actions, but this commit to solve this PR enforces step names. My error on going to this route http://localhost:3000/book_update/3949/waterfall is
Wicked::Wizard::InvalidStepError in Books::BookUpdateController#waterfall
The requested step did not match any steps defined for this controller.
I suppose I should spark up a new controller and tuck the non restful actions into there, but alternatives would be great.
You need to add:
skip_before_filter :setup_wizard, only: :waterfall
in your wicked controller
Related
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 4 years ago.
Improve this question
I'm new to Ruby on rails, and in a month i will start a course on Ruby-on-Rails, but i would like to get some code going before i start the course since i want to learn as much as i can.
I made a project with:
rails new portfolio
Then i did:
rails generate controller portfolio index
To get the front page going.
Rails.application.routes.draw do
get 'portfolio/index'
resources :company
root 'portfolio#index'
end
then:
rails generate controller company
controller:
class CompanyController < ApplicationController
def new
end
end
Then i made a file under views/portfolio/ called index.html.erb where my front page will be.
Under views/company/ i will have a file called company.
When i now go to the url: localhost:3000/company/company i get the error:
The action 'show' could not be found for CompanyController
My CompanyController is this:
class CompanyController < ApplicationController
def new
end
end
Anyone that knows alot about ruby that can just give me a little pointer in the right direction?
Thanks.
Start with a single model and controller for companies. Create a index method inside the app/controllers/companies_controller. Then create the content inside file app/views/companies/index.html.erb to check that everything works, for example:
<h1> Hi! This is root page and index method in CompaniesController! </h1>
In config/routes.rb, you must specify a plural name for companies if you plan to create and process more than one, and leave it as it is, if the entity is the only one company for this project. Set plural name for this resource for this moment to create standard routes for CRUD:
resources :companies
root to: "companies#index"
More about routes you can find in rails guide.
You can try using the built-in scaffold generator in order to quickly generate the application skeleton:
rails generate scaffold companies
The command above will generate controller, model, views and routes with CRUD methods in controller and views for the controller methods. Each view in app/views/"resource_name_plural" adjusted with method in controller in config/routes.rb file. This is how the MVC pattern works.
If you want to create static pages, maybe you should look at the high_voltage gem.
In Rails you need to pay careful attention to pluralization. When declaring routes for a resource it should always be the plural form unless its the rare case where the resource really is singular (there can be only one).
Rails.application.routes.draw do
resources :companies
end
This will route to all companies at /companies and a single company at /companies/:id. If you thus try to get /companies/company it will be routed to the #show action since /company will be interpreted as the id.
Controllers should also be named in plural:
# app/controllers/companies_controller.rb
class CompaniesController < ApplicationController
before_action :set_company, only: [:show, :edit, :update, :destroy]
# GET /companies
def index
#companies = Company.all
end
# GET /companies/:id
def show
end
# ...
private
def set_company
#company = Company.find(params[:id])
end
end
You can use the scaffold command to get a full example of a standard rails CRUD controller:
rails g scaffold companies
If you have the create method, or new, you have to include the show method and index method.
in your routes
resources :companies
resources :portfolios
try in your controller:
class CompanyController < ApplicationController
def new
end
def new
end
def index
#companies = Company.all
end
def show
end
end
and if you have the controller you can create the views and point without problem
localhost:3000/company/company
This, I think it should be something more like this.
localhost:3000/companies/
always plural.
Your controller will look for the view defined in the method. look how it is by default, that of a project of mine. in your place of groups will be companies.
look at the name of the views, are the same names of the methods of your controller. right?
I have two models:
Student
Classroom
Both of them have an action that does the same exact thing: it shows a report of daily activity. That is:
/students/1
/classrooms/1
Grabs activity for the model in question and displays it on the page.
In an attempt to dry this up, I created a ReportsController which extracts all the common logic of building a report.
If I leave the routes like this:
/students/1/report
/classrooms/1/report
Then I can have the ReportsController#show action look for params for :student_id or :classroom_id to determine which model type it is dealing with (for purposes of querying the database and rendering the correct view).
But I would prefer the URLs to be cleaner, so I also changed my routes.rb file to pass the show action for these models to the reports#show controller action:
resources :students, :classrooms do
member do
get :show, to: 'reports#show'
end
end
This works, but I can no longer depend on params to identify which model to work with and which view to render.
Question: should I parse request.fullpath for the model? Or is there a better way to make a shared controller understand which model it is working with?
Routing both show methods to the same controller method for code reuse is somewhat like banging a nail in with a dumptruck.
Even if you can find the resource by looking at the request url you would start splitting the ResortsController into a bunch of ifs and switches even before you got off the ground.
One solution is to add the common action in a module:
module Reporting
extend ActiveSupport::Concern
def show
# the Student or Classroom should be available as #resource
render 'reports/show'
end
included do
before_action :find_resource, only: [:show]
end
private
def find_resource
model = self.try(:resource_class) || guess_resource_class
#resource = model.find(params[:id])
end
# This guesses the name of the resource based on the controller name.
def guess_resource_class
self.class.name[0..-11].singularize.constantize
end
end
class StudentController < ApplicationController
include Reporting
end
# Example where resource name cannot be deduced from controller
class PupilController < ApplicationController
include Reporting
private
def resource_class
Student
end
end
self.class.name[0..-11].singularize.constantize is basically how Rails uses convention over configuration to load a User automatically in your UsersController even without any code.
But the most important key to DRY controllers is to keep your controllers skinny. Most functionality can either be moved into the model layer or delegated out to service objects.
I would put the common logic in the Event Model:
#Event Model
class Event < ...
def self.your_event_method
#self here will be either student.events or classroom.events
#depending on which controller called it
end
end
class StudentsController < ...
...
def show
student = Student.find(params[:id])
student.events.your_event_method
end
end
class ClassroomsController < ...
...
def show
classroom = Classroom(params[:id])
classroom.events.your_event_method
end
end
I have CanCan and Rolify set up with ActiveAdmin, and now it's time to force authorization on my controllers.
Do I have to authorize_resource on every controller (We have a couple dozen models and controllers now), or is there a way to apply it to all of my ActiveAdmin controllers?
I tried calling it in a before_filter from ActiveAdmin.setup, but that didn't work.
I made an initializer: config/initializers/active_admin-cancan.rb
module ActiveAdmin
class ResourceController
# If you don't skip loading on #index you will get the exception:
#
# "Collection is not a paginated scope. Set collection.page(params[:page]).per(10) before calling :paginated_collection."
load_resource :except => :index
authorize_resource
def scoped_collection
end_of_association_chain.accessible_by(current_ability)
end
end
end
Borrowed from another user's code, but now I can't find the source any more.
I have a question regarding the reuse of code among controller actions. I think it is a fairly standard situation, so I am interested in what's the best practice in Rails.
Let's say I have a films resource with a corresponding FilmsController, which has a nested resource comments served by CommentsController. The nested resource can be rendered on its own using its index and show actions. However, it should also be possible to render the comments embedded in the corresponding film page.
Now, the question goes, what is the best way to reuse the code from CommentsController within FilmsController.show?
1) Force the CommentsController.index to render to a string and then pass it in a variable to the film view?
Or 2) call the CommentsController.index directly in the film view as a kind of "partial", executing the database queries from there?
Or 3) create a separate method in CommentsController responsible for the database handling, call it from both CommentsController.index and FilmsController.show, and use the corresponding view in both the places, too?
To me the options 1) and 2) seem a bit messy, while 3) is not modular and involves some repeating of code. Is there any better way to accomplish this?
Thanks a lot!
Now, the question goes, what is the best way to reuse the code from CommentsController within FilmsController.show?
You could move the shared controller logic into a inside your application controller (or a lib and require it appropriately), a la:
class ApplicationController < ActionController::Base
def foo
#foo = "foo"
end
end
Comments Controller:
class CommentsController < ApplicationController
before_filter :foo, :only => [:index]
def index
end
end
Films Controller:
class FilmsController < ApplicationController
before_filter :foo, :only => [:show]
def show
end
end
For repeated view logic you can move that to a common folder, say your_app/app/views/shared/_foo.html.erb and render that appropriately.
Another option is to place the relevant code into an external module:
lib/mymodule.rb
module MyModule
def foo
end
end
And then you can include the module inside your controller or anywhere you want access to your foo method.
class CommentsController < ApplicationController
include MyModule
def index
foo()
end
end
In my Rails 3 app, I have a number of models with a boolean field disabled. In controllers for these models, I use a custom action disable to toggle the disabled field using Ajax.
Example (For client),
# routes.rb
resources :clients do
member do
get :toggle_disable, to: 'clients#disable', as: :disable
end
end
# clients_controller.rb
def disable
#client = Client.find(params[:id])
#client.update_attribute :disabled, !#client.disabled
render 'clients/update_client', format: :js
end
# update_client.js.erb
$('#client-<%= #client.id %>-details').html("<%= escape_javascript(render 'clients/client', client: #client) %>");
I have this code for at least ten resources in my application.
QUESTION
How do I go about DRYing up this code and add actions for these boolean fields dynamically? I could have gone with creating a parent controller or module but I am not sure how will I take care of the views code.
I should be able to do something like this
#clients_controller.rb
class ClientsController < ApplicationController
add_toggle_action :disable
end
Two main ways to share methods:
inheritance: define your action in ApplicationController
mixins: add your method in a module and include the module in the appropriate controllers
Since you want only some controllers to get the method, I'll head towards mixin.
Your controller action must use a view with a full path, not relative, something like:
render '/shared/clien/update', format: :js
Lastly, you'll have to define all routes.