# RecordCollection
[](http://travis-ci.org/bterkuile/record_collection)
record\_collection is a gem that adds functionality to rails to work
with collections. This consists of a few components:
* Collection objects containing some active record models and acting on
that collection.
* the multi\_select helpers for selecting records from the index page
* the optionals helpers for managing attributes on the collection of
records you may or may not want to edit in the collection form
## Installation
Add this line to your application's Gemfile:
```ruby
gem 'record_collection'
```
And then execute:
$ bundle
Or install it yourself as:
$ gem install record_collection
## Adding routes
Add two collection routes to the normal resources definition.
This call behaves exactly as the normal resources :... call,
but adds:
```ruby
collection do
get :collection_edit
post :collection_update
end
```
So the route definition in `config/routes.rb` defined as:
```ruby
collection_resources :employees, except: [:new]
```
is exactly the same as:
```ruby
resources :employees, except: [:new] do
collection do
get :collection_edit
post :collection_update
end
end
```
## Defining the collection
A good practice is to define your collection as a subclass of your
resource class. So an employees collection should be defined like:
`app/models/employee.rb`:
```ruby
class Employee < ActiveRecord::Base
# attribute :admin, type: Boolean (defined by database)
validates :name, presence: true
end
```
`app/models/employee/collection.rb`:
```ruby
class Employee::Collection < RecordCollection::Base
attribute :name
validates :section, format: { with: /\A\w{3}\Z/ }
attribute :admin, type: Boolean
attribute :vegan, type: Boolean
end
```
See the [active_attr](https://github.com/cgriego/active_attr) gem for
attribute definitions.
### Validations
The validations for the collection are exactly the same as your
active_model validations. The only difference is that the allow_nil:
true option standard true is. Since a nil value of a collection attribute
means you do not want to change that value for the individual records.
To make an attribute explicitly required for a collection add the
allow_nil option:
```ruby
validates :email, email: true, allow_nil: false
```
If the update on a record by the collection results in an invalid the
record will not be updated and the collection will not (yet) give the
feedback. The future idea is to create a `#invalid_records` attribute
that will contain those records
### The `.record_class` attribute
The record collection needs to know the class of the records it is
containing, since it need to share some of its behaviour. To do this a
collection assumes that it is subclassed by the model, eg:
```ruby
class Project::Prince2::Collection < RecordCollection::Base
end
Project::Prince2.record_class #=> Project::Prince2
```
If this is not the case, you have to define the record_class manually:
```ruby
class MyAwesomeCollection < RecordCollection::Base
self.record_class = LpRecord
end
```
## Defining your controllers
If you already used the specification `collection_resources :employees` in
your [config/routes.rb](spec/dummy/config/routes.rb) file you can add
the actions in your controller typically looking like:
```ruby
class EmployeesController < ApplicationController
# your standard actions here
# GET /employees/collection_edit?ids[]=1&ids[]=3&...
def collection_edit
@collection = Employee::Collection.find(params[:ids])
redirect_to employees_path, alert: 'No employees selected' if @collection.empty?
end
# POST /employees/collection_update
def collection_update
@collection = Employee::Collection.find(params[:ids])
if @collection.update params[:collection]
redirect_to employees_path, notice: 'Collection is updated'
else
render 'collection_edit'
end
end
end
```
For more advanced use of the collection the pattern above can of course
be different eg: different collection objects for the same active record
model types.
## Creating your views
The
[app/views/employess/collection_edit.html.slim](spec/dummy/app/views/employees/collection_edit.html.slim) view is a tricky one.
Since we are working on a collection of record, and want to edit those
attributes we just want a normal form for editing the attributes,
treating the collection as the record itself. The problem however is
that some attributes can be in a mixed state, say two employees, one
having `admin => true`, the other one `admin => false`. If I only want
to update the section they are both in, I want to leave the admin
attribute allone. To accomplish this, this gem provides the `optional`
helpers. These helpers make it easy to manage a form of attributes where
you can determine which attributes you want to manage for this
particular collection of records. This gem also support [simple_form](https://github.com/plataformatec/simple_form)
gem where you can replace `f.input :attribute, ...etc` with
`f.optional_input :attribute, ...etc`. Our current example works with
the standard [form_helpers](http://guides.rubyonrails.org/form_helpers.html)
### currently supported helpers:
* `optional_boolean`
* `optional_text_field`
* `optional_input` ([simple_form](https://github.com/plataformatec/simple_form))
The form you create typically looks like [app/views/employees/collection_edit.html.slim](spec/dummy/app/views/employees/collection_edit.html.slim):
```slim
h1 Edit multiple employees
= form_for @collection, url: [:collection_update, @collection] do |f|
= f.collection_ids
.form-inputs= f.optional_text_field :section
.form-inputs= f.optional_boolean :admin
.form-inputs= f.optional_boolean :vegan
.form-actions= f.submit
.page-actions
= link_to 'Back', employees_path
```
That is the view part. Be sure to read the optionals section for a
better understanding of how the optional fields work.
## Selecting records from the index using checkboxes (multi_select)
![Screenshot](docs/screenshot-multi-select-1.png?raw=true)
The idea behind working with collections is that you end up as a `GET` request at:
`+controller+/collection_edit?ids[]=2&ids[]=3` etc. How you achieve this
is totally up to yourself, but this gem provides you with a nice
standard way of selecting records from the index page. To filter records
to a specific subset the [ransack](https://github.com/activerecord-hackery/ransack)
gem also provides a nice way to add filtering to the index page. To add
checkbox selecting to your page this gem assumes the following
structure using the [Slim lang](http://slim-lang.com/)
[app/views/employees/index.html.slim](spec/dummy/app/views/employees/index.html.slim)
```slim
h1 Listing Employees
table.with-selection
thead
tr
th Name
th Section
tbody
- @employees.each do |employee|
tr data-record=employee.attributes.to_json
td= employee.name
td= employee.section
```
Note that each row needs a json version of the record at least
containing its id.
Implement the multiselect dependencies in your manifest files, typically
being `app/assets/javascripts/application.js`:
```javascript
//= require record_collection/multi_select
// Or require record_collection/all for all components
```
And for the styling provided by this gem ([app/assets/stylesheets/application.css](spec/dummy/app/assets/stylesheets/application.css.sass)):
```css
/*
*= require record_collection/multi_select
* Or require record_collection/all for all components
*/
```
The styling uses the [font-awesome-rails](http://fortawesome.github.io/Font-Awesome/) gem, so this gem should be
present in your `Gemfile`:
```ruby
gem 'font-awesome-rails'
```
Of course you are welcome to create your own awesome styling and send it
to me so I can add it as a theme :smile:.
To activate multi_select for your page put in your jQuery onload
function:
```javascript
$(function(){
$(document).multi_select()
});
```
You can also apply it to dynamically loaded html replacing document for
the html added to your page.
## Optionals
![Screenshot](docs/screenshot-optionals-1.png?raw=true)
![Screenshot](docs/screenshot-optionals-2.png?raw=true)
Optionals is the name for the feature in this gem that activates
collection attributes to be sumitted in the form or not. Since for a
mixed collection on an attribute you might not want to edit, but another
attribute you do want to edit you add the optionals functionality to
your manifests. This is similar to the `multi_select` feature:
```javascript
//= require record_collection/optionals
// Or require record_collection/all for all components
```
And for the styling provided by this gem ([app/assets/stylesheets/application.css](spec/dummy/app/assets/stylesheets/application.css.sass)):
```css
/*
*= require record_collection/optionals
* Or require record_collection/all for all components
*/
```
To activate the optionals for your page put in your jQuery onload
function:
```javascript
$(function(){
$(document).optionals()
});
```
You can also apply it to dynamically loaded html replacing document for
the html added to your page.
**TODO: more and better explanation about optionals**
## I18n translations
To manipulate the name of a collection and the standard `f.submit` form
label text add the following translation file
[config/locales/record_collection.en.yml](spec/dummy/config/locales/record_collection.en.yml)
```yml
en:
activerecord:
collections:
employee: Group
helpers:
submit:
collection:
create: "Update %{model}"
```
## Special thanks
Special thanks for this project goes to:
## Contributing
1. Fork it ( https://github.com/bterkuile/record_collection/fork )
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create a new Pull Request