by 10KB
Whiteprint keeps track of the attributes of your models. It:
- Generates migrations for you if you update your model's whiteprint (only ActiveRecord at the moment)
- Provides you with helpers to use in your serializers or permitted attributes definition
- Can be extended with plugins
- Has support for inheritance and composition
Add this line to your application's Gemfile:
gem 'whiteprint'
And then execute:
$ bundle
Or install it yourself as:
$ gem install whiteprint
classCarincludeWhiteprint::ModelendAlternatively, in an ActiveRecord model you could also use has_whiteprint.
classCar < ActiveRecord::Basehas_whiteprintendclassCar < ActiveRecord::BaseincludeWhiteprint::Modelwhiteprintdostring:brand,default: 'BMW'string:nametext:descriptiondecimal:price,precision: 5,scale: 10endendLet Whiteprint generate a migration to update your database schema for you (only ActiveRecord at the moment). Run:
rake whiteprint:migrate
Whiteprint will check all your models for changes and list them in your terminal. If multiple models have changes it will ask you if you want to apply these changes in one or separate migrations.
Whiteprint has detected 1 changes to your models.
+----------------------------+------------------------+--------------------------------------------+
| 1. Create a new table cars |
+----------------------------+------------------------+--------------------------------------------+
| name | type | options |
+----------------------------+------------------------+--------------------------------------------+
| brand | string | {:default=>"BMW"} |
| name | string | {} |
| description | text | {} |
| price | decimal | {:precision=>10, :scale=>5} |
| timestamps | | |
+----------------------------+------------------------+--------------------------------------------+
Migrations:
1. In one migration
2. In separate migrations
How would you like to process these changes?
> 1
How would you like to name this migration?
> Create cars
Your migration wil be created and migrated.
# db/migrate/*********_create_cars.rbclassCreateCars < ActiveRecord::Migrationdefchangecreate_table:carsdo |t|
t.string:brand,{:default=>"BMW"}t.string:name,{}t.text:description,{}t.decimal:price,{:precision=>10,:scale=>5}t.timestampsendendend== 20160905153022 CreateCars: migrating =======================================
-- create_table(:cars)
-> 0.0081s
== 20160905153022 CreateCars: migrated (0.0082s) ==============================
If we make some changes to our Car model and run whiteprint:migrate again, Whiteprint will detect these changes and create a migration to update your table.
classCar < ActiveRecord::BaseincludeWhiteprint::Modelwhiteprintdostring:brand,default: 'Ford'string:namedecimal:price,precision: 10,scale: 5references:colorendend> rake whiteprint:migrate
Whiteprint has detected 1 changes to your models.
+--------+-------------+------------+------------------+--------------------+----------------------+
| 1. Make changes to cars |
+--------+-------------+------------+------------------+--------------------+----------------------+
| action | name | type | type (currently) | options | options (currently) |
+--------+-------------+------------+------------------+--------------------+----------------------+
| added | color | references | | {} | |
| change | brand | string | string | {:default=>"Ford"} | {:default=>"BMW"} |
| remove | description | | | | |
+--------+-------------+------------+------------------+--------------------+----------------------+
Migrations:
1. In one migration
2. In separate migrations
How would you like to process these changes?
1
How would you like to name this migration?
Add color change default brand and remove description for cars
== 20160905162923 AddColorChangeDefaultBrandAndRemoveDescriptionForCars: migrating
-- change_table(:cars)
-> 0.0032s
== 20160905162923 AddColorChangeDefaultBrandAndRemoveDescriptionForCars: migrated (0.0034s)
Whiteprint is made to be persistence layer agnostic, but at this moment only an ActiveRecord adapter is implemented. If you would like to implement an adapter for another persistence layer please contact us. We'd love to help you.
An example of a Whiteprint adapter:
moduleWhiteprintmoduleAdaptersclassMyOwnAdapater < ::Whiteprint::Baseclass << selfdefapplicable?(model)# method used to automatically select an adapter for a model.# for example:model < MyOrm::Baseenddefgenerate_migration(name,trees)# create a migration here given a set of trees with changes# look at the activerecord adapter for further implementation detailsendenddefpersisted_attributes# this method has to return the current attributes of the persistance layer# return an instance of Whiteprint::Attributesend# The whiteprint do ... end block in your model is executed in the context of your adapter instance# you can add methods to add functionality to your adapter. For example:defaddress(name)@attributes.addname: "#{name}_street",type: :text@attributes.addname: "#{name}_house_number",type: :integer@attributes.addname: "#{name}_city",type: :textend# And then you could do:# class Company < MyOrm::Base# include Whiteprint::Model## whiteprint do# address :office# end# endendendendThe ActiveRecord adapter has some special properties which are explained in this section.
By default the adapter will add id and timestamps columns. You can disable this behaviour by passing arguments to the whiteprint method.
Model without an id:
whiteprint(id: false)do# ...endModel without timestamps:
whiteprint(timestamps: false)do# ...endAdding an references columns will automatically set a belongs_to association on the model. Any options for the association can be passed in the whiteprint block.
whiteprintdoreferences:fileable,polymorphic: trueendYou can disable this behaviour by passing auto_belongs_to: false to the whiteprint method.
The activerecord adapter has support for a has_and_belongs_to_many attribute. This won't add a column to your model's table, but instead create a join table and set the association.
whiteprintdohas_and_belongs_to_many:categoriesendhabtm is added as an alias
You can specify a symbol as the default value for an attribute for dynamic defaults.
classCar < ActiveRecord::BaseincludeWhiteprint::Modelwhiteprintdoreferences:user,default: :current_userendprivatedefcurrent_userUser.currentendendYou can use the accessor type to add attr_accessors to your model
classUser < ActiveRecord::BaseincludeWhiteprint::Modelwhiteprintdotext:password_digestaccessor:passwordaccessor:password_confirmationendendThe whiteprint instance of a model can be accessed using the whiteprint method: Model.whiteprint. The attributes of a whiteprint are available using the attributes method: Model.whiteprint.attributes.
These attributes are an instance of Whiteprint::Attributes and have several helper methods available.
The for_serializer helper lists all attributes that aren't private or associations.
classCar < ActiveRecord::BaseincludeWhiteprint::Modelwhiteprintdostring:brand,default: 'Ford'string:namedecimal:price,precision: 10,scale: 5,private: truereferences:colorendendCar.whiteprint.attributes.for_serializer# [:id, :created_at, :updated_at, :brand, :name]# usage exampleclassCarSerializer < ActiveModel::Serializerattributes *Car.whiteprint.attributes.for_serializerendThe for_serializer helper lists all attributes that aren't private or readonly in a format suitable for Rails' strong paramters.
classCar < ActiveRecord::BaseincludeWhiteprint::Modelwhiteprintdostring:brand,default: 'Ford'string:name,readonly: truetext:specs,array: truedecimal:price,precision: 10,scale: 5,private: truereferences:colorhabtm:owners,class_name: 'User'endendCar.whiteprint.attributes.for_permitted# [:id, :brand, {:specs=>[]}, :color_id, {:owner_ids=>[]}]# usage exampledefpermitted_paramsparams.require(:car).permit(*Car.whiteprint.attributes.for_permitted)endThe for_meta helper lists all meta info that is specified for attributes. The meta_attribute_options config determines which options should be listed by this helper.
Whiteprint.configdo |c|
c.meta_attribute_options=[:enum,:label]endclassCar < ActiveRecord::BaseincludeWhiteprint::Modelwhiteprintdostring:brand,default: 'Ford',enum: {"Ford"=>"Ford","BMW"=>"BMW","Audi"=>"Audi"},label: 'Merk'string:name,label: 'Naam'text:specs,array: truedecimal:price,precision: 10,scale: 5,private: truereferences:colorhabtm:owners,class_name: 'User'endendCar.whiteprint.attributes.for_meta# {:brand=>{:enum=>{"Ford"=>"Ford", "BMW"=>"BMW", "Audi"=>"Audi"}, :label=>"Merk"}, :name=>{:label=>"Naam"}}# example usagerenderjson: @car,meta: Car.whiteprint.attributes.for_metaWhiteprints are inherited and can be composed.
classAnimalincludeWhiteprint::Modelwhiteprintdotext:nametext:descriptionendendmoduleMammalextendActiveSupport::Concernincludeddowhiteprintdointeger:gestation_periodendendendclassDog < AnimalincludeMammalwhiteprintdostring:breedendendclassCat < AnimalincludeMammalwhiteprintdoboolean:domestic,default: trueendendCat.whiteprint.attributes.to_a.map(&:to_h)# [{:name=>:name, :type=>:text}, {:name=>:description, :type=>:text}, {:name=>:gestation_period, :type=>:integer}, {:name=>:domestic, :type=>:boolean, :default=>true}]Dog.whiteprint.attributes.to_a.map(&:to_h)#[{:name=>:name, :type=>:text}, {:name=>:description, :type=>:text}, {:name=>:gestation_period, :type=>:integer}, {:name=>:breed, :type=>:string}]Whiteprint.configdo |c|
# which adapter to use if none is applicablec.default_adapter=:base# Models have to be loaded before whiteprint:migrate runs. Set to true to let Whiteprint do this for you.c.eager_load=false# default true for Rails projects# Define which path(s) contain whiteprint modelsc.eager_load_paths=[]# Define which attribute options are persistedc.persisted_attribute_options={array: false,limit: nil,precision: nil,scale: nil,polymorphic: false,null: true,default: nil}# Define the attribute options for the for_meta gelperc.meta_attribute_options=[:enum]# Define if changes should be run in a single or separately migrations. One of: :ask, :separately, :togetherc.migration_strategy=:ask# Define if migrations should be automatically added to gitc.add_migration_to_git=falseendWhiteprint is extracted from an application framework we use internally. Right now, our framework is lacking tests and documentation, but we intend to open source more parts of our framework in the future.