Chassis is a collection of new classes and enhancements to existing projects for building maintainable applications. I choose the name "chassis" because I'm a car guy. A chassis is a car's foundation. Every car has key components: there is an engine, transmission, differential, suspension, electrical system, and a bunch of other things. They fit together on the chassis in a certain way, there are guidelines but no one is going to stop you from building a custom front suspension on a typical chassis. And that's the point. The chassis is there to build on. It does not make decisions for you. There are also kit cars and longblock engines. Kit cars come with some components and rely on you to assemble them. Longblocks are halfway complete engines. The engine block and valve train are predecided. You must decide which fuel delivery and exhaust system to use. Then you mount it in the chassis. In all things there is a line between prepackaged DIY and turn-key solutions. This project is a combination of a chassis and long block. Some things have been predecided and others are left to you. In that sense this project is a utility belt. All the components are there, you just need to figure out how to put them together.
This project chooses an ideal gem stack for building web applications and enhancements to existing projects. It's just a enough structure to build an application. It is the chassis you build your application on.
Here's an example I put together.
Chassis is implemented with help from a few smaller libraries. A unified interface if you do not want to know about such things.
- Errors with TNT
- Object initialization with Lift
- Interchangeable objects with Interchange
Add this line to your application's Gemfile:
gem 'chassis'
And then execute:
$ bundle
Or install it yourself as:
$ gem install chassis
Right off the bat, chassis is for building web applications. It depends on other gems to make that happen. Chassis fully endorses rack & Sinatra as the best way to do this. So it contains enhancements and middleware to make that so.
Chassis::Rack::Bouncer- takes a block. Used to bounce spam or other undesirable requests.Chassis::Rack::HealthCheck- for load balanced applications. Takes a block to test if the applications is ready. Failures terminate the process.Chassis::Rack::Instrumentation- use harness to instrument all request timingsChassis::Rack::NoRobots- blocks all crawlers and bots.
Chassis::WebService includes some of these middleware as well as
other customizations.
- requires
sinatra/jsonfor JSON response generation - requires
rack/contrib/bounce_favictonbecause ain't no body got time for that - uses
Chassis::Rack::Bouncer - uses
Chassis::Rack::NoRobots - uses
Rack::Deflatorto gzip everything - uses
Rack::PostBodyContentTypeParserto parse incoming JSON bodies enable :corsto enable CORS with manifold.- registers error handlers for unknown exceptions coming from other chassis components.
- other misc helpers for generating JSON and handling errors.
Chassis includes a
repository using
the query pattern as well. The repository pattern is perfect because
it does not require knowledge about your persistence layer. It is the
access layer. A null, in-memory, and Redis adapter are included. You
can subclass these adapters to make your own.
Chassis::Repo::Delegation can be included in other classes to
delegate to the repository.
Here's an example:
class CustomerRepo
extend Chassis::Repo::Delegation
endNow there are CRUD methods available on CustomerRepo that delegate
to the repository for Customer objects. Chassis::Persistence can
be included in any object. It will make the object compatible with
the matching repo.
class Customer
include Chassis::Persistence
endNow Customer responds to id, save, and repo. repo looks for
a repository class matching the class name (e.g. CustomerRepo).
Override as you see if.
More on my blog here.
Virtus and virtus-dirty_attribute are used to create
Chassis::Form. It includes a few minor enhancements. All assignments
go through dirty tracking to support the partial update use case.
Chassis::Form#values will return a hash of everything that's been
assigned. Chassi::Form#attributes returns a hash for all the
declared attributes. initialize has been modified as well. Trying to
set an unknown attributes will raise
Chassis::Form::UnknownFieldError instead of NoMethodError.
Chassis::WebService registers an error handler and returns a 400 Bad Request in this case.
Create a new form by including Chassis.form
class SignupForm
include Chassis.form
endChassis uses Faraday because it's the best god damn HTTP client in ruby. Chassis includes a bunch of middleware to make it even better.
Farday.new 'http://foo.com', do |builder|
# Every request is timed with Harness into a namespaced key.
# You can pass a namespace as the second argument: IE "twilio",
# or "sendgrid"
faraday.request :instrumentation
# Send requests with `content-type: application/json` and use
# the standard library JSON to encode the body
faraday.request :encode_json
# Parse a JSON response into a hash
faraday.request :parse_json
# This is the most important one IMO. All requests 4xx and 5xx
# requests will raise a useful error with the response body
# and status code. This is much more useful than the bundled
# implementation. A 403 response will raise a HttpForbiddenError.
# This middleware also captures timeouts.
# Useful for catching failure conditions.
faraday.request :server_error_handler
# Log all requests and responses. Useful when debugging running
# applications
faraday.response :logging
endThere is also a faraday factory that will build new connections using this middleware stack.
# Just like normal, but the aforementioned middleware included.
# Any middleware you insert will come after the chassis ones.
Chassis.faraday 'http://foo.com' do |builder|
# your stuff here
endBreaker provides the low level
implementation. Chassis::CircuitPanel is a class for unifying
access to all the different circuits in the application. This is
useful because other parts of the code don't need to know about how
the circuit is implemented. Chassis.circuit_panel behaves like
Struct.new. It returns a new class.
CircuitPanel = Chassis.circuit_panel do
circuit :test, timeout: 10, retry_threshold: 6
end
panel = CircuitPanel.new
circuit = panel.test
circuit.class # => Breaker::Circuit
circuit.run do
# do your stuff here
endSince Chassis.circuit_panel returns a class, you can do anything you
want. Don't like to have to instantiate a new instance every time? Use
a singleton and assign that to a constant.
require 'singleton'
CircuitPanel = Chassis.circuit_panel do
include Singleton
circuit :test, timeout: 10, retry_threshold: 6
end.instance
CircuitPanel.test.run do
# your stuff here
endA proxy object used to track assignments. Wrap an object in a dirty session to see what changed and what it changed to.
Person = Struct.new :name
adam = Person.new 'adam'
session = Chassis::DirtySession.new adam
session.clean? # => true
session.dirty? # => false
session.name = 'Adman'
session.dirty? # => true
session.clean? # => false
session.named_changed? # => true
session.changed # => set of values changed
session.new_values # => { name: 'Adman' }
session.original_values # => { name: 'adam' }
session.reset! # reset everything back to normalChassis includes the logger-better gem to refine the standard
library logger. Chassis::Logger default the logdev argument to
Chassis.stream. This gives a unified place to assign all output.
The log level can also be controlled by the LOG_LEVEL environment
variable. This makes it possible to restart/boot the application with
a new log level without redeploying code.
A very simple implementation of the observer pattern. It is different from the standard library implementation for two reasons:
- you don't need to call
changedfornotify_observersto work. notify_obseversincludesselfas first argument to all observers- there is only the
add_observermethod.
- 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