New Feature!
- We're integrating with Google Sheet!
- …yeah, I'm gonna need you to have that done by Friday…
Awww, CRUD
- Create
- Read
- Update
- Delete
- Profit!
Adapt and Survive
module DataMapper module Adapters class GoogleSheetAdapter < DataMapper::Adapters::AbstractAdapter # ... end end end
Create Incremental
def create(resources) # ... end
Create - Group by table
def create(resources) table_groups = group_resources_by_table(resources) table_groups.each do |table, resources| # ... end end
Create - Initialize serial fields
Initializing serial fields…
resources.each do |resource| initialize_serial(resource, worksheet_record_count(table) + 1) # ... end
…makes this to work:
class User property :id, Serial end
Create - post record
resources.each do |resource| # ... post_resource_to_worksheet(resource, table) end
Create - return count
def create(resources) # ... resources.size end
Create - done!
Now we can do this:
class LogEntry include DataMapper::Resource property :exercise, String property :reps, Integer end LogEntry.create(:exercise => "push-ups", :reps => 30)
Read Incremental
def read(query) # ... end
Read - fetch the table
def read(query) worksheet_name = query.model.storage_name(name) list = worksheet_as_list(worksheet_name) # ... end
Read - transform data
DataMapper expects an Enumerable of {Property => value}
hashes to be
returned.
def read(query) # ... resource_hashes = list_to_hashes(list, query.fields) # ... end
Read - almost done
We can already do this:
LogEntry.all # => all entries LogEntry.first # => first entry in the table
Read - filter results
def read(query) # ... query.filter_records(resource_hashes) end
Read - done!
Now we can do this:
LogEntry.all(:reps.gt => 30, :limit => 10, :order => [:reps.desc])
Read - progressive enhancement
Optimize as-needed.
# ... when GreaterThanComparison then ">" when LessThanComparison then "<" when GreaterThanOrEqualToComparison then ">=" # ...
Update Incremental
def update(attributes, collection) # ... end
Update - put updated records
def update(attributes, collection) each_resource_with_edit_url(collection) do |resource, edit_url| put_updated_resource(edit_url, resource) end # ... end
Update - return a count
def update(attributes, collection) # ... collection.size end
Update - done!
Now we can do this:
log_entry.update(:comment => "increase weight")
Delete Incremental
def delete(collection) # ... end
Delete - delete records
def delete(collection) each_resource_with_edit_url(collection) do |resource, edit_url| connection.delete(edit_url, 'If-Match' => "*") end # ... end
Delete - return a count
def delete(collection) # ... collection.size end
Delete - done!
Now we can do this:
log_entry.destroy LogEntry.destroy # clear the table
Customize table naming
def field_naming_convention ->(property){ property.name.to_s.gsub(/[^[:alnum:]]+/, '').downcase } end
Create/delete tables
def create_model_storage(model) # ... end def upgrade_model_storage(model) # ... end def destroy_model_storage(model) # ... end
Auto-migration
DataMapper.auto_migrate! # or: DataMapper.auto_upgrade!
Very handy for testing/development.
New feature!
- SQL is the new NoSQL
- Need all the Sheet data back in the DB
- ASAP!
Copying records across repositories
DataMapper.setup(:other, 'sqlite::memory:') DataMapper.repository(:other).auto_migrate! LogEntry.copy(:default, :other)
Who cares about writing adapters?
DataMapper's killer app is that it lowers the bar for experimenting with different forms of data storage.
The Rosetta Stone of data
- dm-mongo-adapter
- dm-redis-adapter
- dm-rdf-adapter
- dm-imap-adapter
- dm-adapter-simpledb
- dm-tokyo-adapter
- etc…
Friendly to adapter writers
- Easy things easy, hard things possible
- AST representation of queries
- Incrementally add features, optimizations
- Great documentation, readable code
- Lots of conveniences
- Plugins give you free functionality
Plugins
- Validation
- DB constraints
- Pagination
- State machine
- Tagging
- Aggregation
- Versioning
- Trees/lists/nested sets
- etc…
About DataMapper
Based on the Data Mapper pattern
A layer of Mappers that moves data between objects and a database while keeping them independent of each other and the mapper itself.
A Mature Library
- In development since early 2008 (?)
- Hit 1.0 in June
- API is now stable
Everything's a lazy scope
entries = LogEntry.all pushups = entries.all(:name => "push-ups") squats = entries.all(:name => "squats") combined = pushups & squats combined.to_a # NOW the datastore is queried
The Identity Map
ActiveRecord:
big_bird = Monster.create!(:name => "Big Bird") snuffy = Monster.create!(:name => "Snuffleupagus") snuffy.friends << big_bird snuffy.friends.first.name # => "Big Bird" snuffy.friends.first.equal?(big_bird) # => false
The Identity Map
DataMapper:
big_bird = Monster.create(:name => "Big Bird") snuffy = Monster.create(:name => "Snuffleupagus") snuffy.friends << big_bird snuffy.friends.first.name # => "Big Bird" snuffy.friends.first.equal?(big_bird) # => true
Natural-key friendly
class ZipCode property :code, String, :key => true property :state, String end # ... ZipCode.get("17361")
Legacy-friendly
class LogEntry storage_names[:legacy] = 'tblEntry' # ... end
Awesome community
#datamapper on Freenode
As much DataMapper as you want
- Use alongside ActiveRecord
- Or replace ActiveRecord entirely
rails new project_name \ -m http://datamapper.org/templates/rails.rb
Thank You!
- Rate!
http://spkr8.com/t/7286
- Contact
- Home: http://avdi.org
- Email: avdi@avdi.org
- Twitter: @avdi