# RecordCollection [Build Status](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:
Companytools    FourStack    ## 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