Now that TimelogController
has most of the REST actions it needs, I’m able to convert it to a REST resource.
Before
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
# config/routes.rb map.connect 'time_entries/:id/edit', :action => 'edit', :controller => 'timelog' map.connect 'time_entries/:id', :action => 'update', :controller => 'timelog', :conditions => {:method => :put} map.connect 'projects/:project_id/time_entries/new', :action => 'new', :controller => 'timelog' map.connect 'projects/:project_id/issues/:issue_id/time_entries/new', :action => 'new', :controller => 'timelog' map.with_options :controller => 'timelog' do |timelog| timelog.connect 'projects/:project_id/time_entries', :action => 'index' timelog.with_options :action => 'index', :conditions => {:method => :get} do |time_details| time_details.connect 'time_entries' time_details.connect 'time_entries.:format' time_details.connect 'issues/:issue_id/time_entries' time_details.connect 'issues/:issue_id/time_entries.:format' time_details.connect 'projects/:project_id/time_entries.:format' time_details.connect 'projects/:project_id/issues/:issue_id/time_entries' time_details.connect 'projects/:project_id/issues/:issue_id/time_entries.:format' end timelog.connect 'projects/:project_id/time_entries/report', :controller => 'time_entry_reports', :action => 'report' timelog.with_options :controller => 'time_entry_reports', :action => 'report',:conditions => {:method => :get} do |time_report| time_report.connect 'time_entries/report' time_report.connect 'time_entries/report.:format' time_report.connect 'projects/:project_id/time_entries/report.:format' end timelog.with_options :action => 'new', :conditions => {:method => :get} do |time_edit| time_edit.connect 'issues/:issue_id/time_entries/new' end timelog.connect 'projects/:project_id/timelog/edit', :action => 'create', :conditions => {:method => :post} timelog.connect 'time_entries/:id/destroy', :action => 'destroy', :conditions => {:method => :post} end |
After
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
# config/routes.rb map.connect 'projects/:project_id/time_entries/report', :controller => 'time_entry_reports', :action => 'report' map.with_options :controller => 'time_entry_reports', :action => 'report',:conditions => {:method => :get} do |time_report| time_report.connect 'time_entries/report' time_report.connect 'time_entries/report.:format' time_report.connect 'projects/:project_id/time_entries/report.:format' end # TODO: wasteful since this is also nested under issues, projects, and projects/issues map.resources :time_entries, :controller => 'timelog' map.resources :issues, :member => { :edit => :post }, :collection => {} do |issues| issues.resources :time_entries, :controller => 'timelog' end map.resources :issues, :path_prefix => '/projects/:project_id', :collection => { :create => :post } do |issues| issues.resources :time_entries, :controller => 'timelog' end map.resources :projects, :member => { :copy => [:get, :post], :settings => :get, :modules => :post, :archive => :post, :unarchive => :post } do |project| # ... project.resources :time_entries, :controller => 'timelog', :path_prefix => 'projects/:project_id' end |
This was one of the more difficult refactorings I’ve done, because of all the potential ways a timelog can be accessed.
Time Entry Reports
1 2 3 4 5 6 |
map.connect 'projects/:project_id/time_entries/report', :controller => 'time_entry_reports', :action => 'report' map.with_options :controller => 'time_entry_reports', :action => 'report',:conditions => {:method => :get} do |time_report| time_report.connect 'time_entries/report' time_report.connect 'time_entries/report.:format' time_report.connect 'projects/:project_id/time_entries/report.:format' end |
First, I extracted the routing for the TimeEntryReportsController
. This was embedded in the map.with_options
section before but there was no reason to leave it in there with the resource.
Base Time Entries
1 |
map.resources :time_entries, :controller => 'timelog' |
Next I converted the base time entries at the root of Redmine to a resource. This is the standard “/time_entries” and “/time_entries/1234” routing that is common with Rails apps.
Issue Time Entries
1 2 3 |
map.resources :issues, :member => { :edit => :post }, :collection => {} do |issues| issues.resources :time_entries, :controller => 'timelog' end |
Then I needed to handle issue specific routing of time entries, like “/issues/100/time_entries” and “/issues/100/time_entries/1234”. For this I defined a nested resource under the :issues
resource.
Project Time Entries
1 2 3 4 5 6 7 8 9 10 |
map.resources :projects, :member => { :copy => [:get, :post], :settings => :get, :modules => :post, :archive => :post, :unarchive => :post } do |project| # ... project.resources :time_entries, :controller => 'timelog', :path_prefix => 'projects/:project_id' end |
I also needed to handle project specific routing like, “/projects/my-project/time_entries” and “/projects/my-project/time_entries/1234”. So underneath the :projects
resource I added another nested resource, just like I did for :issues
.
Project and Issue Time Entries
1 2 3 |
map.resources :issues, :path_prefix => '/projects/:project_id', :collection => { :create => :post } do |issues| issues.resources :time_entries, :controller => 'timelog' end |
Finally I needed to handle both project and issue specific routing, like “/projects/my-project/issues/100/time_entries” and “/projects/my-project/issues/100/time_entries/1234”. For this I used a nested resource underneath the other :issues
resource. Since that :issues
resource is using a :path_prefix
, it will pick up the project part of the url that I need.
The good news is that I now have enough of Redmine converted to REST routes that I can start to refactor the nested routes using the :shallow
option. Hopefully this will really clean up the routing and make it easy to start using resources as links (e.g. issues_path
, project_issues_path(@project)
). This will also make adding a REST API much easier now that some of the controllers follow a standard format.