Gather data. Skip boiler plate code.
Use Gatherable to dynamically create active record models, controllers for those models, and routes for those controllers. You can even use Gatherable to create Javascript objects to talk to your new routes.
Gatherable was conceived with the purpose of gathering information about the user experience. The idea is to gather various data points and link them via a global identifier, so that relations between them can later be analyzed
Getting Started
Include the gem in your Gemfile
gem 'gatherable'
Define what to gather
In the root of your project, run
rails generate gatherable initializer
This will create following config/initializers/gatherable.rb file
Gatherable.configure do |c|
c.global_identifier = :gatherable_id
# c.data_point :data_point_name, :data_point_type, OPTIONS - see below
c.data_point :price, :decimal
#c.data_table :table_name, { column_name: :column_type, column_name2: :column_type }, OPTIONS - see below
c.data_table :requested_loan_amount, { requested_loan_amount: :decimal, total_cost: :decimal, monthly_repayment_amount: :decimal }
# for both data tables and data points, you'll automatically get a primary key 'table_name_id',
# an indexed global identifier, and timestamps
#If want your db schema to be something besides 'gatherable', uncomment the line below
#c.schema_name = 'foo_bar'
end
Name your global_identifier however you want (you have ta have
one, sorry), and whatever data points you want to collect. Each data
point will later become its own table (see below).
Should you want more complex tables, use the data_table syntax shown above.
Generate migrations
Gatherable will auto-generate the migrations to support the data points
you want to collect. The generated tables will live in a created
gatherable schema.
rails generate gatherable migrations
(you'll also want to run the migrations, of course)
Mount the engine
In config/routes.rb, mount the gatherable engine. It'll look something
like this:
Rails.application.routes.draw do
#whatever routes your app already has
mount Gatherable::Engine => "/gatherable"
end
Gather!
Models, controllers, and routes are dynamically defined when you configure gatherable, so you're ready to roll!
For each data point you're collecting, there are up to five controller methods already defined for you (you get to choose, see the options section).
For a data point called price, the routes for these methods would look
like this:
GET /prices(.:format) => #index
POST /prices(.:format) => #create
GET /prices/:price_id(.:format) => #show
PUT /prices/:price_id(.:format) => #update
DELETE /prices/:price_id(.:format) => #destroy
The create and update methods for gatherable data points requires a
specific param format, like so:
{ data_point_name: { attr_1: 'foo', attr_2: 'bar' } }
Use the given js file
Add the following line to your application.js file:
//= require gatherable
This gives you a js object that makes ajax calls to the routes mentioned above. The method signatures look like this:
create: function(gatherableVar, options, globalIdentifier = null)
show: function(gatherableVar, id, globalIdentifier = null)
update: function(gatherableVar, id, options, globalIdentifier = null)
index: function(gatherableVar, globalIdentifier = null)
destroy: function(gatherableVar, id, globalIdentifier = null)
# gatherableVar - the name of the data point / table
# options a {column_name: value} js object
# globalIdentifier: needed if you've explicitly made it a part of your routes (see prefixed_resources, below)
Seeing the magic
If you want to customize the models or controllers that Gatherable dynamically defines, simply generate them:
rails generate gatherable models
rails generate gatherable controllers
rails generate gatherable javascripts
Options
Both data_points and data_tables take an optional options hash.
Valid keys and values are outlined below.
-
:new_record_strategy-
:insert- when a new record isPOSTed, insert it into the db -
:update- instead of immediately inserting, see if there already exists a record with the same global id. Yes? update it. No? insert - defaults to
:insert - see below for more information
-
-
:controller_actions- an array containing any of the following:indexshowcreateupdatedestroy- defaults to
[:show, :create]
The configuration also has additional options, enumerated below
-
prefixed_resources- Gatherable ties data points together using yourglobal_identifier. Adding a resource (data point) to theprefixed_resourcesarray means that the routes for that resources will all start with the global_identifier, i.e.GET /:session_id/prices
For the full list of routes, see the gather! section above
-
auth_methodIf you're using prefixed resources, you can ensure theglobal_identifiervalue being passed is equivalent to the one for this user's session;auth_method = :session
If you're not using prefixed resources, gatherable uses sessions behind the scenes to ensure that a user can only view / manipulate their own data.
Saving some space
If you're using this to track user interactions, you may find that this
engine creates quite a bit of data. In order to alleviate that, you can
use new_record_strategy for your data points / tables
data_point :price, :decimal, new_record_strategy: :update #default is :insert
When using the update strategy, models will try to find a record with
the same global_identifier and update it. A record will be inserted if
there are none with a matching gatherable_id
Conversely, the insert strategy will not add an updated_at column to
your migrations, since you'll be inserting a new record each time
Credit
This was written partially on Enova's time / dime