I have a Rails 2 app with an API. I versioned and optimised the controllers but there are duplicate methods. The goal is to have the common information in only one place. So I explored the following options:
redirect from routes the non-API controller, but each controller needs it's specific hooks
module inclusion. This is my favorite but there are like quite a lot of errors thrown out and very limited time to fix things up.
eval. Put all the code in one file and eval it in both places. Done this, it works but I am not pleased by this workaround.
What would be the best to go about this?
Might be some typos lurking in here but:
class GenericController < ApplicationController
def index
#objects = params[:controller].singularize.camelcase.constantize.all()
end
def show
#object = params[:controller].singularize.camelcase.constantize.find(params[:id])
end
def new
#object = params[:controller].singularize.camelcase.constantize.new
end
def edit
#object = params[:controller].singularize.camelcase.constantize.find(params[:id])
end
def create
model = params[:controller].singularize.downcase
#object = params[:controller].singularize.camelcase.constantize.new(params[model])
if #object.save
redirect_to '/'+params[:controller]
else
render :action => 'new'
end
end
def update
model = params[:controller].singularize.downcase
#object = params[:controller].singularize.camelcase.constantize.find(params[:id])
if #object.update_attributes(params[model])
redirect_to :controller => params[:controller], :action => 'index'
else
render :action => 'edit'
end
end
def destroy
if #object = params[:controller].singularize.camelcase.constantize.find(params[:id])
#object.destroy
end
redirect_to :controller => params[:controller], :action => 'index'
end
end
Specific controllers can override those implementations as needed, but:
class ProjectsController < GenericController
# done!
end
class ScenariosController < GenericController
# done!
end
Related
UPDATE: I have solved the NilClass issue! Thanks!
Now I am having a problem with:
unknown attribute 'sessionId' for Room.
SOLVEDI am currently having some issues where my code is telling me I have an error of "undefined method `create_session' for nil:NilClass" on line 9. I will provide the files.
This is the specific line:
#new_room = Room.new(strong_param)
rooms_controller.rb
class RoomsController < ApplicationController
require "opentok"
before_filter :config_opentok,:except => [:index]
def index
#rooms = Room.where(:public => true).order("created_at DESC")
#new_room = Room.new
end
def create
session = #opentok.create_session :media_mode => :routed
params[:room][:sessionId] = session.session_id
#new_room = Room.new(strong_param)
respond_to do |format|
if #new_room.save
format.html { redirect_to(“/party/”+#new_room.id.to_s) }
else
format.html { render :controller => ‘rooms’, :action => “index” }
end
end
end
def party
#room = Room.find(params[:id])
#tok_token = #opentok.generate_token #room.sessionId
end
private
def config_opentok
if #opentok.nil?
#opentok = OpenTok::OpenTok.new ########, "#########################################"
end
end
def strong_param
params.require(:room).permit(:name,:sessionId)
end
end
rooms.rb (Models)
class Room < ActiveRecord::Base
end
I've tried several different modifications to these files to make my program work. I can get the listing page to work but once I try and actually create a new room, I receive this error message.
Look forward to any advice you can provide.
You are missing the before_filter :config_opentok,:except => [:index] line from the blog post in your previous post (https://railsfornovice.wordpress.com/2013/01/01/video-chatting-in-ruby-on-rails/)
How can I create a dynamic function name in rails using the data in the database? I don't know if this is even possible.
here is a sample of my goal
class PageController < ApplicationController
def (PageModel.find(1)) #def stay
#codes here #codes here
end #end
end
I know the syntax is wrong. please help, thanks
Update
this function will only be called via routes, and in my routes I have this line
match "/:action", :controller => "page", :via => "get"
the function will look like this if it is manually generated
def stay
#some query
render 'stay_page', :layout => 'stay_page_layout'
end
def pleasure
#some query
render 'pleasure _page', :layout => 'pleasure _page_layout'
end
In routes.rb:
# Either this...
get "pages/:page", to: "pages#page"
# Or this, but make sure this is the last route in the file
get "/:page", to: "pages#page"
Your controller:
class PageController < ApplicationController
def page
#page = PageModel.find(params[:page])
end
end
If you just need to render different templates depending on the page, you can do this:
def page
#page = PageModel.find(params[:page])
render #page.name
end
If you need custom logic you could do something like this:
VALID_PAGES = ["contact_us"]
def page
#page = PageModel.find(params[:page])
execute(#page.name)
end
def execute(name)
if VALID_PAGES.include?(name)
send(name)
end
end
def contact_us
# do stuff
end
Assuming your PageModel have page_id and page_name, your url get page_id as an parameter, and will render with page_name.
code for controller:
class PageController < ApplicationController
def dynamic_action
page_name = PageModel.find(params[:page_id]).page_name
render "#{page_name}_page", :layout => "#{page_name}_page_layout"
end
end
route:
match '/:page_id' => 'page#dynamic_action'
I have a welcome wizzard that builds a user profile when first login.Problem is it is quite messy implemented but I have tried to refactor it several times and rewrite it but cannot comeup with something better then below.
In ideal world it would be all inside welcome_controller.rb but this have caused much headache so Now i rewrote the update method for profile_controller instead.
Any thoughts on how to improve this make it more dry and clean? Would love to recieve some good input on this and thoughts perhaps to move all update stuff to welcome controller instead?
WelcomeController:
class WelcomeController < ApplicationController
before_filter :authenticate_user!
before_filter :load_step
layout "welcome"
def sub_layout
"center"
end
def edit
# form updates post to edit since
# profile is non existant yet
params[:step] = "photos" unless params[:step]
#photos = Photo.where(:attachable_id => current_user.id)
#profile = Profile.where(:user_id => current_user.id).first
#photo = Photo.new
if ["photos", "basics", "details", "test"].member?(params[:step])
# force rendering the correct step
case current_user.profile.step
when 1
render :template => "/profiles/edit/edit_photos", :layout => "welcome"
when 2
render :template => "/profiles/edit/edit_basics", :layout => "welcome"
when 3
render :template => "/profiles/edit/edit_details", :layout => "welcome"
when 4
render :template => "/profiles/edit/edit_test", :layout => "welcome"
end
else
render :action => "/profiles/edit/edit_photos"
end
end
def load_step
redirect_to root_path if current_user.profile.complete
case current_user.profile.step
when 1
redirect_to "/welcome" unless params[:controller] == "welcome"
when 2
redirect_to "/welcome/basics" unless params[:controller] == "welcome" && params[:action] == "edit" && params[:step] == "basics"
when 3
redirect_to "/welcome/details" unless params[:controller] == "welcome" && params[:action] == "edit" && params[:step] == "details"
when 4
redirect_to "/welcome/test" unless params[:controller] == "welcome" && params[:action] == "edit" && params[:step] == "test"
end
end
end
ProfileController:
class ProfileController < ApplicationController
...
def update
#profile = Profile.find(params[:id])
#tags = Session.tag_counts_on(:tags)
#profile.form = params[:form]
#match = Match.where(:user_id => current_user.id).first
authorize! :update, #profile
respond_to do |format|
if #profile.update_attributes(params[:profile])
if current_user.profile.complete
format.html { redirect_to "/profiles/#{ current_user.username }/edit/#{ #profile.form }", notice: t('notice.saved') }
else
case current_user.profile.step
when 1
current_user.profile.update_attributes(:step => 2)
format.html { redirect_to "/welcome/basics", notice: t('notice.saved') }
when 2
current_user.profile.update_attributes(:step => 3)
format.html { redirect_to "/welcome/details", notice: t('notice.saved') }
when 3
current_user.profile.update_attributes(:step => 4)
format.html { redirect_to "/welcome/test", notice: t('notice.saved') }
end
end
else
if current_user.profile.complete
format.html { render action: "/edit/edit_" + params[:profile][:form], :what => #profile.form }
else
case current_user.profile.step
when 1
current_user.profile.update_attributes(:step => 2)
format.html { redirect_to "/welcome/basics", notice: t('notice.saved') }
when 2
current_user.profile.update_attributes(:step => 3)
format.html { redirect_to "/welcome/details", notice: t('notice.saved') }
when 3
current_user.profile.update_attributes(:complete => 1)
format.html { redirect_to root_path }
end
end
end
end
end
...
end
Views are in /profiles/edit/*
Wizards are notoriously difficult to get right and I've never seen an implementation that fully satisfied me. I usually go with so called "form objects" and create a restful controller for each step.
There is an excellent (but paid) Railscast on the subject.
The gist is this: You make an object that quacks just like a regular ActiveRecord model, by using ActiveModel.
For instance:
class Welcome::BasicInformation
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
def persisted?
false
end
def initialize(user)
#user = user
end
attr_reader :user
delegate :some_field, :some_other_field, to: :user
validates_presence_of :some_field
def save(params)
user.some_field = params[:some_field]
user.some_other_field = params[:some_other_field]
if valid?
user.step = 2
user.save
end
end
def photo
#photo ||= Photo.new
end
def profile
#profile ||= user.profiles.first
end
end
You'd basically create a model like this for every step.
Then you can create controllers for each step, with a specialized ApplicationController for all the steps:
class Welcome::ApplicationController < ::ApplicationController
layout "welcome"
before_filter :authentice_user!
end
And for each step:
class Welcome::BasicInformationsControlller < Welcome::ApplicationController
def new
#step = Welcome::BasicInformation.new(current_user)
end
def create
#step = Welcome::BasicInformation.new(current_user)
if #step.save(params[:welcome_basic_information])
redirect_to welcome_some_other_step_path, notice: "Yay"
else
render :new
end
end
end
And create a route for each step:
namespace :welcome do
resource :basic_information, only: [:new, :create]
resource :some_other_step, only: [:new, :create]
end
This only leaves some automatic redirects to do, like prohibiting users from going to steps that they're not yet allowed to visit. This might not be as important now that you're using separate URLs for each step.
You can store information about which step to visit in the form objects:
class Welcome::BasicInformation
# ...
def allowed?
user.profile.step == 1
end
end
And then refactor the controllers a bit:
class Welcome::BasicInformationsController < Welcome::ApplicationController
before_filter :allowed?
def new
end
def create
if step.save(params[:welcome_basic_information])
redirect_to welcome_some_other_step_path, notice: "Yay"
else
render :new
end
end
private
def step
#step ||= Welcome::BasicInformation.new(current_user)
end
helper_method :step
def allowed?
redirect_to previous_step_path unless step.allowed?
end
end
This might not be shorter, but I do like how flexible it is, how focussed each step is, how you can do different validations on each step and so on. Each controller/model combination is very easy to follow and will be understandable for others.
There are a couple of things I'd do, but first some thoughts.
Sometimes you have to break restfullness a little to make code more readable. That's the case
It's not a good manner to redirect between controllers as you do in here
So, what I'd do.
Put all the code concerning those steps in a single controller (profile preferably) and adjust url with routing.
Create a single show and single save action
If I understand properly the step that will be shown to user depends ONLY on what User#step is set on current_user. Threfore I think there's really no need to pass any url variables, you can get current/next step from current_user.
Code refactored (may be some errors, didn't test that)
All in ProfileController
def edit
#profile = Profile.find(current_user.id)
#next_step = current_user.step.to_i + 1 # I imply that there's just single permissable next step
render :template => "/profiles/edit/#{#next_step}", :layout => "welcome"
end
def update
#profile = Profile.find(params[:id])
authorize! :update, #profile
if #profile.update_attributes(params[:profile])
# you should pass step number in params so I get's updated by default.
redirect_to "/welcome/basics", notice: t('notice.saved')
else
end
end
Using RSpec and Cancan, I have this test which fails with:
Failure/Error: User.should_receive(:new).and_return(#user)
expected: 1 time
received: 2 times
because
load_and_authorize_resource seems to call 'new' on the object as well. How do I work around this?
it "creates a new staff member" do
User.should_receive(:new).and_return(#user)
get :new, :format => "js"
end
-
class Admin::UsersController < ApplicationController
load_and_authorize_resource
def new
#user = User.new()
respond_to do |format|
format.js { render :action => "new" }
end
end
end
Well:
either replace load_and_authorize_resource with authorize_resource
Or:
remove #user = User.new() (which bears unnecessary parentheses)
You're definitely doing things twice here.
How can I manage multiple non-related Models from a Rails form?
Should I have only one controller for all Models or for each Model one controller?
this is my controller:
class WordsController < ApplicationController
def new
#word=Word.new
#verb=Verb.new
#adjektiv=Adjektiv.new
#adverb=Adverb.new
end
def create
#word=Word.create(params[:word])
#verb=Verb.create(params[:verb])
#adjektiv=Adjektiv.create(params[:adjektiv])
#adverb=Adverb.create(params[:adverb])
if #word.save || #verb.save || #adjektiv || #adverb
redirect_to :action => 'index'
else
render :action => 'new'
end
end
def index
#word=Word.find(:all)
#verb=Verb.find(:all)
#adjektiv=Adjektiv.find(:all)
#adverb =Adverb.find(:all)
end
def edit
#word=Word.find(params[:id])
#verb=Verb.find(params[:id])
#adjektiv=Adjektiv.find(params[:id])
#adverb =Adverb.find(params[:id])
end
def update
#word =Word.find(params[:id])
if #word.update_attributes(params[:word]) || #verb.update_attributes(params[:verb]) || #adjektiv.update_attributes(params[:adjektiv]) || #adverb.update_attributes(params[:adverb])
redirect_to :action => 'index'
else
redirect_to :action => 'edit'
end
end
end
You can manage several models from one form. ( But I think use missed something in application architecture if you want to do it as you explained ). You should not need several controllers - use one. For ex.: just create new instances of models in new action, validate and save them in create action. Same for edit/update actions.
I think you also should use form_tag instead of form_for beacause there will be no parent model to process.