rails/rails — pull request #15719 by ujjwalt (2014-06-14 09:34:40+00:00)
Title: Collection Routing for Rails

As part of Google Summer of Code 2013, I worked on implementing a collection routing api for Rails under the mentorship of @pixeltrix.
Majority of the work is over with enough stability to be considered for merging. I would like to open up the discussion on the api. The documentation still remains but this is on purpose considering the community might vote for changes.
### Features
#### Generate collection routes by including a collection option.
```ruby
resources :posts, collection: true
# rake routes
# Prefix Verb URI Pattern Controller#Action
# posts GET /posts(/:ids)(.:format) posts#index {:ids=>/(?:[^\.\/\?]|\.\.)+/, :collection=>true}
# POST /posts(.:format) posts#create {:collection=>true}
# PUT /posts/:ids(.:format) posts#replace {:ids=>/(?:[^\.\/\?]|\.\.)+/, :collection=>true}
# PATCH /posts/:ids(.:format) posts#update_many {:ids=>/(?:[^\.\/\?]|\.\.)+/, :collection=>true}
# DELETE /posts/:ids(.:format) posts#destroy_many {:ids=>/(?:[^\.\/\?]|\.\.)+/, :collection=>true}
# edit_posts GET /posts/:ids/edit(.:format) posts#edit_many {:ids=>/(?:[^\.\/\?]|\.\.)+/, :collection=>true}
# new_post GET /posts/new(.:format) posts#new
# edit_post GET /posts/:id/edit(.:format) posts#edit
# post GET /posts/:id(.:format) posts#show
# PATCH /posts/:id(.:format) posts#update
# PUT /posts/:id(.:format) posts#update
# DELETE /posts/:id(.:format) posts#destroy
```

#### Pass ranges as parameters and filter them using the Strong Parameters API
Ranges can now be passed as url parameters. Following are some examples :-
```
http://example.com/posts/1..10,17,45...89 => 1 to 10, 17 and 45 to 88
http://example.com/posts/1..10,7 => 1 to 10
http://example.com/posts/1,5,9 => 1, 5 and 9
```

```ruby
filtered_params = params.permit ids: 1...768
filtered_params = params.permit ids: 69..420
```

#### Generate collection routing enabled controllers using scaffolds
`rails generate scaffold Posts --collection`
```ruby
class PostsController < ApplicationController
before_action :set_post, only: [:show, :edit, :update, :destroy]

# GET /posts/1..10,17
def index
@posts = post_ids ? Post.find(post_ids) : Post.all
end

# Post ids
def post_ids
@posts_ids ||= params.permit(ids: 1..2**32-1)
end

# GET /posts/1
def show
end

# GET /posts/new
def new
@post = Post.new
end

# GET /posts/1/edit
def edit
end

# POST /posts
def create
posts_params ? create_many : create_one
end

def create_many
@posts = Post.create(posts_params)
redirect_to posts_url(@posts), notice: 'Posts were successfully updated.'
end

def create_one
@post = Post.create(post_params)
redirect_to post_url(@post), notice: 'Post was successfully updated.'
end

# PATCH/PUT /posts/1
def update
if @post.update(post_params)
redirect_to @post, notice: 'Post was successfully updated.'
else
render action: 'edit'
end
end

# DELETE /posts/1
def destroy
@post.destroy
redirect_to posts_url, notice: 'Post was successfully destroyed.'
end

# Collection routes
# PATCH /posts/1..10
def update_many
Post.find(post_ids).each do |post|
post.update(params[:posts][:"#{post.id}"])
end
redirect_to @post, notice: 'Posts were successfully updated.'
end

# PUT /posts/1..10
def replace
Post.destroy_all
create
end

# DELETE /users/1..10
def destroy_many
@posts = Post.destroy(posts_ids)
end

private
# Use callbacks to share common setup or constraints between actions.
def set_post
@post = Post.find(params[:id])
end

# Only allow a trusted parameter "white list" through.
def post_params
params[:post]
end

# Only allow a trusted collection parameter "white list" through.
def posts_params
params[:posts]
end
end
```
Scaffold generators of dependencies like JBuilder need to be updated to incorporate collection routing.
Future work will include reducing `replace` and `update_many` to single line atomic database operations using ActiveRecord.