ActiveManageable
ActiveManageable provides a framework from which to create business logic "manager" classes in your Ruby on Rails application. Thus extending the MVC pattern to incorporate a business logic layer that sits between the controllers and models.
Moving your busines logic into a separate layer provides benefits including:
- skinny controllers & models
- reusable code that reduces duplication across application & API controllers and background jobs
- isolated unit tests for the business logic, allowing system & integration tests to remain true to their purpose of testing user interaction and the workflow of the application
- clear separation of concerns with controllers responsible for managing requests, views dealing with presentation and models handling attribute level validation and persistence
- clear & consistent interface
ActiveManageable business logic manager classes
- include methods for the seven standard CRUD actions: index, show, new, create, edit, update, and destroy
- can be configured to incorporate authentication, search and pagination logic
- enable specification of the associations to eager load, default attribute values, scopes & order when retrieving records and more
- perform advanced parsing of parameter values for date/datetime/numeric attributes
To show how ActiveManageable manager classes can be used to create DRY code in skinny controllers, we’ll refactor the following controller index method that retrieves records with an eager loaded association using Pundit, Ransack & Kaminari.
def index
search = policy_scope(User).ransack(params[:q])
search.sorts = "name asc" if q.sorts.empty?
authorize(User)
@users = search.result.includes(:address).page(params[:page])
end
With ActiveManageable configured to use the Pundit, Ransack & Kaminari libraries, the following manager class includes the standard CRUD methods and sets the default order and association to eager load in the index method.
class UserManager < ActiveManageable::Base
manageable ActiveManageable::ALL_METHODS
default_order :name
default_includes :address, methods: :index
end
Using the manager class, the controller index method can now be rewritten to only include a single call to the index method.
def index
@users = UserManager.new.index(options: {search: params[:q], page: {number: params[:page]}})
end
The manager classes provide standard implementations of the seven core CRUD methods. These can be extended or overwritten to perform custom business logic and the classes can also be extended to include the business logic for additional actions, both making use of the internal ActiveManageable methods and variables described in the Adding Bespoke Methods section.
With an Activity model in a CRM application to manage meetings & tasks, a complete action may be required. This could be implemented as follows:
class ActivityManager < ActiveManageable::Base
manageable ActiveManageable::ALL_METHODS
def complete(id:)
initialize_state
@target = model_class.find(id)
authorize(record: @target, action: :complete?)
@target.update(completed_by: current_user.id, completed_at: Time.zone.now)
end
end
The controller method can then call the manager method, retrieve the activity that was completed and act on the result.
def complete
result = manager.complete(id: params[:id])
@activity = manager.object
# now redirect based on the result
end
Installation
Add this line to your application's Gemfile:
gem 'active_manageable'
And then execute:
bundle install
Or install it yourself as:
gem install active_manageable
Table of Contents
- Configuration
- Current User
- Authorization
- Class Definition
- Manageable Method
- Default Includes
- Default Attribute Values
- Default Select
- Default Order
- Default Scopes
- Unique Search
- Default Page Size
- Paginate Without Count
- Current Method
- Index Method
- Authorization Scope
- Search Option
- Page Option
- Order Option
- Scopes Option
- Includes Option
- Select Option
- Distinct
- Show Method
- Includes Option
- Select Option
- New Method
- Create Method
- Edit Method
- Includes Option
- Update Method
- Includes Option
- Destroy Method
- Includes Option
- Extending the CRUD Methods
- Build and Retrieve Scoped Records
- Attribute Value Parsing
- Date and DateTime Attribute Values
- Numeric Attribute Values
- ActiveManageable Attributes
- Adding Bespoke Methods
- Development
- Contributing
- License
- Code of Conduct
Configuration
Create an initializer to configure the optional in-built authorization, search and pagination libraries to use.
ActiveManageable.config do |config|
config.authorization_library = :pundit # or :cancancan
config.search_library = :ransack
config.pagination_library = :kaminari
end
These library configuration options can also be set to a module in order to use a custom authorization, search or pagination implementation.
When eager loading associations the includes
method is used by default but this can be changed via a configuration option that accepts :includes
, :preload
or :eager_load
ActiveManageable.config do |config|
config.default_loading_method = :preload
end
ActiveManageable will attempt to determine the model class to use based on the class name and subclass_suffix
configuration option. So if the class is named "AlbumManager" and an Album
constant exists that will be used as the model class. If you want to use a suffix other than "Manager", the configuration option can be changed or alternatively each class can specify the model class to use when calling the manageable
method.
ActiveManageable.config do |config|
config.subclass_suffix = "Concern"
end
class BusinessLogic < ActiveManageable::Base
manageable ActiveManageable::ALL_METHODS, model_class: Album
end
Current User
ActiveManageable uses its own current_user
per-thread module attribute when performing authorization with one of the configuration libraries. This needs to be set before using its methods, for example in an ApplicationController
filter.
around_action :setup_request
def setup_request
ActiveManageable.current_user = current_user
yield
ActiveManageable.current_user = nil
end
The current_user
can also be set or overridden for a block using the with_current_user
method.
manager = AlbumManager.new
manager.with_current_user(user) do
manager.show(id: 1)
end
And is accessible via an instance method.
manager = AlbumManager.new
manager.current_user
Authorization
When using one of the configuration authorization libraries, each of the methods will perform authorization for the current user, method and either model class or record. If authorization fails an exception will be raised so you may choose to rescue the relevant exception.
Pundit - Pundit::NotAuthorizedError
CanCanCan - CanCan::AccessDenied
Class Definition
Manageable Method
Create a class that inherits from ActiveManageable::Base
then use the manageable
method to specify which methods should be included. Use the ActiveManageable::ALL_METHODS
constant to include all methods (ie. :index, :show, :new, :create, :edit, :update and :destroy) or pass the required method name symbol(s).
class AlbumManager < ActiveManageable::Base
manageable ActiveManageable::ALL_METHODS
end
class SongManager < ActiveManageable::Base
manageable :index, :show
end
Default Includes
The default_includes
method sets the default associations to eager load when fetching records in the index, show, edit, update and destroy methods. These defaults are only used if the :options
argument for those methods does not contain a :includes
key.
class AlbumManager < ActiveManageable::Base
manageable ActiveManageable::ALL_METHODS
default_includes :songs
end
It accepts a single, array or hash of association names, optional :methods
in which to eager load the associations and optional :loading_method
if this needs to be different to the configuration :default_loading_method
. It also accepts a lambda/proc to execute to return associations with optional :methods
.
default_includes :songs, :artist, methods: [:index, :show]
default_includes songs: :artist, loading_method: :preload, methods: [:edit, :update]
default_includes -> { destroy_includes }, methods: :destroy
def destroy_includes
[:songs, {artist: :songs}]
end
Default Attribute Values
The default_attribute_values
the default attribute values to use when building a model object in the new and create methods. These defaults are combined with the attribute values from :attributes
argument for those methods. When default and argument values contain the same attribute key, the value from the argument is used.
class AlbumManager < ActiveManageable::Base
manageable ActiveManageable::ALL_METHODS
default_attribute_values genre: "pop"
end
It accepts either a hash of attribute values or a lambda/proc to execute to return a hash of attribute values and optional :methods
in which in which to use the attribute values.
default_attribute_values genre: "pop", released_at: Date.current, methods: :new
default_attribute_values -> { create_attrs } , methods: :create
def create_attrs
{genre: "electronic", published_at: Date.current}
end
Default Select
The default_select
method sets the attributes to return in the SELECT statement used when fetching records in the index, show and edit methods. These defaults are only used if the :options
argument for those methods does not contain a :select
key.
class AlbumManager < ActiveManageable::Base
manageable ActiveManageable::ALL_METHODS
default_select :id, :name
end
It accepts either an array of attribute names or a lambda/proc to execute to return an array of attribute names and optional :methods
in which to use the attributes.
default_select :id, :name, :genre, methods: :show
default_select -> { select_attributes }, methods: [:index, :edit]
def select_attributes
[:id, :name, :genre, :released_at]
end
Default Order
The default_order
method sets the default order to use when fetching records in the index method. These defaults are only used if the :options
argument for the method does not contain an :order
key.
class AlbumManager < ActiveManageable::Base
manageable ActiveManageable::ALL_METHODS
default_order :name
end
It accepts attributes in the same formats as the ActiveRecord
order method or a lambda/proc to execute to return attributes in the recognised formats.
default_order "name DESC"
default_order [:name, :id]
default_order -> { order_attributes }
def order_attributes
["name DESC", "id"]
end
Default Scopes
The default_scopes
method sets the default scope(s) to use when fetching records in the index method. These defaults are only used if the :options
argument for the method does not contain a :scopes
key.
class AlbumManager < ActiveManageable::Base
manageable ActiveManageable::ALL_METHODS
default_scopes :electronic
end
It accepts a scope name, a hash containing scope name and argument, or an array of names/hashes. It also accepting a lambda/proc to execute to return a scope name, hash or array.
default_scopes {released_in_year: "1980"}
default_scopes :rock, :electronic, {released_in_year: "1980"}
default_scopes -> { index_scopes }
def index_scopes
[:rock, :electronic]
end
Unique Search
The has_unique_search
method specifies whether to use the distinct method when fetching records in the index method.
class AlbumManager < ActiveManageable::Base
manageable ActiveManageable::ALL_METHODS
has_unique_search
end
It accepts no argument to always return unique records or a hash with :if or :unless keyword and a method name or lambda/proc to execute each time the index method is called.
has_unique_search if: :method_name
has_unique_search unless: -> { lambda }
Default Page Size
When using the Kaminari pagination library, the default_page_size
method sets default page size to use when fetching records in the index method. The default is only used if the :options
argument for the method does not contain a :page
hash with a :size
key.
class AlbumManager < ActiveManageable::Base
manageable ActiveManageable::ALL_METHODS
default_page_size 5
end
Paginate Without Count
When using the Kaminari pagination library, the paginate_without_count
method will result in the index method using the Kaminari without_count
mode to create a paginatable collection without counting the total number of records.
class AlbumManager < ActiveManageable::Base
manageable :index
paginate_without_count
end
It is also possible to control whether to use the without_count
mode globally by setting the paginate_without_count
configuration option.
ActiveManageable.config do |config|
config.paginate_without_count = true
end
And it is also possible to control whether to use the without_count
mode for an individual index method call by including the :without_count
key within the options
argument :page
hash.
manager.index(options: {page: {number: 2, size: 10, without_count: true}})
Current Method
ActiveManageable includes a current_method
attribute which returns the name of the method being executed as a symbol, which can potentially be used within methods in conjunction with a lambda for the default methods described above. Additionally, the method argument options
and attributes
are also accessible as attributes.
default_includes -> { method_includes }
def method_includes
case current_method
when :index
{songs: :artist}
when :show
[:label, :songs]
when :edit, :update
options.key?(:xyz) ? [:label, songs: :artists] : [:label, :songs]
else
:songs
end
end
Index Method
The index
method has an optional options
keyword argument. The options
hash can contain :search
, :order
, :scopes
, :page
, :includes
and :select
keys. The method performs authorization for the current user, method and model class using the configuration library; invokes a block (if provided); retrieves records using the various options described below; and returns the records which are also accessible via the collection
attribute.
manager.index
Index Authorization Scope
When using one of the configuration authorization libraries, the method retrieves records that the current user is authorized to access. For the Pundit authorization library, the method retrieves records filtered using the model's policy scope. For the CanCanCan authorization library, the method retrieves records filtered using the accessible_by scope for the current user's ability.
Index Search Option
When using the Ransack search library, the options
argument :search
key is used to set the Ransack filter and sorting. If either the :search
key or its sorts :s
key is not present, the method will order the records using the standard approach described below. The Ransack search object is accessible via the ransack
attribute.
manager.index(options: {search: {artist_id_eq: 1, s: "name ASC"}})
ransack_search = manager.ransack
Index Page Option
When using the Kaminari pagination library, the options
argument :page
hash is used to set the page number and size of records to retrieve. The page number is set using the :number
key value and page size is set using the :size
key value. If the :size
key is not present, the class default is used and if a class default has not been set then the Kaminari application default is used.
manager.index(options: {page: {number: 2, size: 10}})
Index Order Option
The options
argument :order
key provides the ability to specify the order in which to retrieve records and accepts attributes in the same formats as the ActiveRecord
order
method. When the :order
key is not present, any class defaults are used.
manager.index(options: {order: "name DESC"})
Index Scopes Option
The options
argument :scopes
key provides the ability to specify the scopes to use when retrieving records and accepts a scope name, a hash containing scope name and argument, or an array of names/hashes. When the :scopes
key is not present, any class defaults are used.
manager.index(options: {scopes: :electronic})
manager.index(options: {scopes: {released_in_year: "1980"}})
manager.index(options: {scopes: [:rock, :electronic, {released_in_year: "1980"}]})
Index Includes Option
The options
argument :includes
key provides the ability to specify associations to eager load and accepts associations names in the same formats as the AR includes
method eg. a single association name, an array of names or a hash of names. When the :includes
key is not present, any class defaults are used.
manager.index(options: {includes: [:artist, :songs]})
The :includes
key can also be used to vary the method used to eager load associations by providing :associations
and :loading_method
keys. When the :loading_method
key is not present the method will use either the class default method (set using default_includes
) or the configuration default_loading_method
.
manager.index(options: {includes: {associations: :songs, loading_method: :preload}})
Index Select Option
The options
argument :select
key provides the ability to limit the attributes returned in the SELECT statement. When the :select
key is not present, any class defaults are used.
manager.index(options: {select: [:id, :name, :artist_id, :released_at]})
Index Distinct
If the class has_unique_search
method has been used then this will be evaluated to determine whether to use the distinct method when fetching the records.
Show Method
The show
method has id
and optional options
keyword arguments. The options
hash can contain :includes
and :select
keys. The method invokes a block (if provided); retrieves a record; performs authorization for the current user, method and record using the configuration library; and returns the record which is also accessible via the object
attribute.
manager.show(id: 1)
Show Includes Option
The options
argument :includes
key provides the ability to specify associations to eager load and accepts associations names in the same formats as the AR includes
method eg. a single association name, an array of names or a hash of names. When the :includes
key is not present, any class defaults are used.
manager.show(id: 1, options: {includes: [:artist, :songs]})
The :includes
key can also be used to vary the method used to eager load associations by providing :associations
and :loading_method
keys. When the :loading_method
key is not present the method will use either the class default method (set using default_includes
) or the configuration default_loading_method
.
manager.show(id: 1, options: {includes: {associations: :songs, loading_method: :preload}})
Show Select Option
The options
argument :select
key provides the ability to limit the attributes returned in the SELECT statement. When the :select
key is not present, any class defaults are used.
manager.show(id: 1, options: {select: [:id, :name, :artist_id, :released_at]})
New Method
The new
method has an optional attributes
keyword argument. The attributes
argument is for an ActionController::Parameters
or hash of attribute names and values to use when building the record. The method builds a record; performs authorization for the current user, method and record using the configuration library; invokes a block (if provided); and returns the record which is also accessible via the object
attribute.
manager.new
The attributes
argument values are combined with the class default values and when the default and argument values contain the same attribute key, the value from the argument is used.
manager.new(attributes: {genre: "electronic", published_at: Date.current})
Create Method
The create
method has an attributes
keyword argument. The attributes
argument is for an ActionController::Parameters
or hash of attribute names and values to use when building the record. The method builds a record; performs authorization for the current user, method and record using the configuration library; invokes a block (if provided) and attempts to save the record within a transaction; and returns the save result. The record is also accessible via the object
attribute.
manager.create(attributes: {name: "Substance", genre: "electronic", published_at: Date.current})
The attributes
argument values are combined with the class default values and when the default and argument values contain the same attribute key, the value from the argument is used.
Edit Method
The edit
method has id
and optional options
keyword arguments. The options
hash can contain :includes
and :select
keys. The method invokes a block (if provided); retrieves a record; performs authorization for the current user, method and record using the configuration library; and returns the record which is also accessible via the object
attribute.
manager.edit(id: 1)
Edit Includes Option
The options
argument :includes
key provides the ability to specify associations to eager load and accepts associations names in the same formats as the AR includes
method eg. a single association name, an array of names or a hash of names. The :select
key provides the ability to limit the attributes returned in the SELECT statement. When the :includes
and :select
keys are not present, any class defaults are used.
manager.edit(id: 1, options: {includes: [:artist, :songs], select: [:id, :name, :artist_id, :released_at]})
The :includes
key can also be used to vary the method used to eager load associations by providing :associations
and :loading_method
keys. When the :loading_method
key is not present the method will use either the class default method (set using default_includes
) or the configuration default_loading_method
.
manager.edit(id: 1, options: {includes: {associations: :songs, loading_method: :preload}})
Update Method
The update
method has id
, attributes
and optional options
keyword arguments. The attributes
argument is for an ActionController::Parameters
or hash of attribute names and values to use when updating the record. The options
hash can contain an :includes
key. The method retrieves a record; performs authorization for the current user, method and record using the configuration library; assigns the attributes; invokes a block (if provided) and attempts to save the record within a transaction; and returns the save result. The record is also accessible via the object
attribute.
manager.update(id: 1, attributes: {genre: "electronic", published_at: Date.current})
Update Includes Option
The options
argument :includes
key provides the ability to specify associations to eager load and accepts associations names in the same formats as the AR includes
method eg. a single association name, an array of names or a hash of names. When the :includes
key is not present, any class defaults are used.
manager.update(id: 1, attributes: {published_at: Date.current}, options: {includes: [:artist]})
The :includes
key can also be used to vary the method used to eager load associations by providing :associations
and :loading_method
keys. When the :loading_method
key is not present the method will use either the class default method (set using default_includes
) or the configuration default_loading_method
.
manager.update(id: 1, attributes: {published_at: Date.current}, options: {includes: {associations: :songs, loading_method: :preload}})
Destroy Method
The destroy
method has id
and optional options
keyword arguments. The options
hash can contain an :includes
key. The method retrieves a record; performs authorization for the current user, method and record using the configuration library; invokes a block (if provided) and attempts to destroy the record within a transaction; and returns the destroy result. The record is accessible via the object
attribute.
manager.destroy(id: 1)
Destroy Includes Option
The options
argument :includes
key provides the ability to specify associations to eager load and accepts associations names in the same formats as the AR includes
method eg. a single association name, an array of names or a hash of names. When the :includes
key is not present, any class defaults are used.
manager.destroy(id: 1, options: {includes: [:artist]})
The :includes
key can also be used to vary the method used to eager load associations by providing :associations
and :loading_method
keys. When the :loading_method
key is not present the method will use either the class default method (set using default_includes
) or the configuration default_loading_method
.
manager.destroy(id: 1, options: {includes: {associations: :songs, loading_method: :preload}})
Extending the CRUD Methods
Each of the seven core CRUD methods include a yield
so it is possible to extend the methods by passing a block.
def create(attributes:)
super do
@target.description = "change the object before it is created using a block"
end
end
The logic within each of the methods has also been split into subsidiary methods so it is possible to extend or override these methods.
def create_object
@target.description = "change the object before it is created via an override"
super
end
Build and Retrieve Scoped Records
Each of the seven core CRUD methods build and retrieve records using the action_scope
method which by default returns the model class. In order to build or retrieve records using a scope it is possible to override this method.
def action_scope
current_user.albums
end
Attribute Value Parsing
Date and DateTime Attribute Values
If you have users in the US where the date format is month/day/year you'll be aware that ActiveRecord
does not support that string format. The issue is further complicated if you also have users in other countries that use the day/month/year format.
I18n.locale = :"en-US"
Album.new(published_at: "12/22/2022 14:21").published_at # => nil
ActiveManageable caters for these different formats and provides greater flexibility to accept a wider variety of formats by parsing date and datetime values using the Flexitime gem before setting a model object's attribute values. Flexitime uses the rails-i18n gem to determine whether the first date part is day or month and then returns an ActiveSupport TimeZone object. ActiveManageable updates the attributes
argument for the new, create and update methods to replace the value for any attributes with a data type of date or datetime and also updates the attributes values for any associations within the attributes hash.
I18n.locale = :"en-US"
ActiveManageable.current_user = User.first
manager = AlbumManager.new
manager.new(attributes: {published_at: "12/01/2022 14:21", songs_attributes: [{published_at: "12/01/2022 14:21"}]})
manager.object.published_at # => Thu, 01 Dec 2022 14:21:00.000000000 UTC +00:00
manager.object.songs.first.published_at # => Thu, 01 Dec 2022 14:21:00.000000000 UTC +00:00
manager.attributes # => {"published_at"=>Wed, 12 Jan 2022 14:21:00.000000000 UTC +00:00, ... }]}
I18n.locale = :"en-GB"
ActiveManageable.current_user = User.first
manager = AlbumManager.new
manager.new(attributes: {published_at: "12/01/2022 14:21", songs_attributes: [{published_at: "12/01/2022 14:21"}]})
manager.object.published_at # => Wed, 12 Jan 2022 14:21:00.000000000 UTC +00:00
manager.object.songs.first.published_at # => Wed, 12 Jan 2022 14:21:00.000000000 UTC +00:00
manager.attributes # => {"published_at"=>Wed, 12 Jan 2022 14:21:00.000000000 UTC +00:00, ... }]}
By default, the Flexitime gem parse
method returns time objects with a minute precision so to persist datetime values with seconds or milliseconds it is necessary to set the Flexitime configuration option accordingly.
ActiveManageable.current_user = User.first
manager = AlbumManager.new
manager.new(attributes: {published_at: "12/01/2022 14:21:45"})
manager.object.published_at # => Wed, 12 Jan 2022 14:21:00.000000000 UTC +00:00
Flexitime.precision = :sec
manager.new(attributes: {published_at: "12/01/2022 14:21:45"})
manager.object.published_at # => Wed, 12 Jan 2022 14:21:45.000000000 UTC +00:00
Numeric Attribute Values
If you have users in the Netherlands or other countries that use a comma number separator then you ideally want to allow them to enter numeric values using that separator rather than a point separator. Unfortunately ActiveRecord
does not support such a separator when setting attributes values.
I18n.locale = :nl
Album.new(length: "6,55").length.to_s # => "6.0"
ActiveManageable caters for the comma number separator by replacing the comma with a point before setting a model object's attribute values. It uses the rails-i18n gem to determine if the locale number separator is a comma. It then updates the attributes
argument for the new, create and update methods to replace the comma for any attributes with a data type of decimal or float and a value that contains only a single comma and no points. It also updates the attributes values for any associations within the attributes hash.
I18n.locale = :nl
ActiveManageable.current_user = User.first
manager = AlbumManager.new
manager.new(attributes: {length: "6,55", songs_attributes: [{length: "8,3"}]})
manager.object.length.to_s # => "6.55"
manager.object.songs.first.length.to_s # => "8.3"
manager.attributes # => {"length"=>"6.55", "songs_attributes"=>[{"length"=>"8.3"}]}
ActiveManageable Attributes
ActiveManageable includes the following attributes:
object
- the record from the show, new, create, edit, update and destroy methods
collection
- the records retrieved by the index method
current_method
- the name of the method being executed as a symbol eg. :show
attributes
- an ActiveSupport::HashWithIndifferentAccess
representation of the argument from the new, create and update methods (in the case of an ActionController::Parameters
the attribute contains only the permitted keys)
options
- an ActiveSupport::HashWithIndifferentAccess
representation of the argument from the index, show, edit, update and destroy methods
ransack
- the Ransack search object used when retrieving records in the index method (when using the Ransack search library)
Adding Bespoke Methods
The manager classes provide standard implementations of the seven core CRUD methods. These can be extended or overwritten to perform custom business logic and the classes can also be extended to include the business logic for additional actions, both making use of the internal ActiveManageable methods and variables.
def complete(id:)
initialize_state
@target = model_class.find(id)
authorize(record: @target, action: :complete?)
@target.update(completed_by: current_user.id, completed_at: Time.zone.now)
end
Each method should first call the initialize_state
method which has optional attributes
and options
keyword arguments. This method sets the @target
variable to nil, sets the @current_method
variable to the name of the method being executed as a symbol (eg. :complete
) and sets the @attributes
and @options
variables after performing attribute values parsing.
The model_class
method returns the ActiveRecord
class set either automatically or manually when calling manageable
.
The @target
instance variable makes the model object or ActiveRecord::Relation
(in the case of the index method) accessible to the internal ActiveManageable methods. For external access, there are less ambiguous alias methods named object
and collection
.
The authorize
method performs authorization for the current user, record and action using the configuration library. The record
argument can be a model class or instance and the action
argument is optional with the default being the method name.
Development
After checking out the repo, run bin/setup
to install dependencies. Then, run rake spec
to run the tests.
You can also experiment in the rails console using the dummy app. Within the spec/dummy directory:
- run
bin/rails db:setup
to create the database, load the schema, and initialize it with the seed data - run
rails c
Then in the console:
ActiveManageable.current_user = User.first
manager = AlbumManager.new
manager.index
After making changes:
- run
rake spec
to run the tests and check the test coverage - run
open coverage/index.html
to view the test coverage report - run
bundle exec appraisal install
to install the appraisal dependencies orbundle exec appraisal update
to upgrade the appraisal dependencies - run
bundle exec appraisal rspec
to run the tests against different versions of activerecord & activesupport - run
bundle exec rubocop
to check the style of files
To install this gem onto your local machine, run bundle exec rake install
.
To release a new version:
- update the version number in
version.rb
- update the change log in
CHANGELOG.md
- run
bundle
to update theGemfile.lock
version - commit the changes
- run
bundle exec rake release
to create & push a git tag for the version and push the.gem
file to rubygems.org. - create a new release for the version on GitHub
Contributing
Bug reports and pull requests are welcome on GitHub. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.
- Fork it
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create new Pull Request
License
The gem is available as open source under the terms of the MIT License.
Code of Conduct
Everyone interacting in the ActiveManageable project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.