Skip to content

Commit

Permalink
Pushing to GitHub, squash all previous messy commits.
Browse files Browse the repository at this point in the history
  • Loading branch information
pauldowman committed Nov 9, 2010
0 parents commit da6f07f
Show file tree
Hide file tree
Showing 20 changed files with 1,230 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .document
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
README.rdoc
lib/**/*.rb
bin/*
features/**/*.feature
LICENSE
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
*.gem
.DS_Store
.bundle
.gitmodel-data
coverage
pkg
rdoc
1 change: 1 addition & 0 deletions .rspec
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--colour
1 change: 1 addition & 0 deletions .rvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
rvm use ruby-1.9.2@gitmodel
5 changes: 5 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Edit this Gemfile to bundle your application's dependencies.
source :rubygems

gemspec

54 changes: 54 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
PATH
remote: .
specs:
gitmodel (0.0.0)
activemodel (>= 3.0.1)
activesupport (>= 3.0.1)
grit (>= 2.3.0)
lockfile (>= 1.4.3)

GEM
remote: http://rubygems.org/
specs:
ZenTest (4.4.0)
activemodel (3.0.1)
activesupport (= 3.0.1)
builder (~> 2.1.2)
i18n (~> 0.4.1)
activesupport (3.0.1)
autotest (4.4.1)
autotest-fsevent (0.2.3)
sys-uname
builder (2.1.2)
diff-lcs (1.1.2)
grit (2.3.0)
diff-lcs (~> 1.1)
mime-types (~> 1.15)
i18n (0.4.1)
lockfile (1.4.3)
mime-types (1.16)
rspec (2.0.1)
rspec-core (~> 2.0.1)
rspec-expectations (~> 2.0.1)
rspec-mocks (~> 2.0.1)
rspec-core (2.0.1)
rspec-expectations (2.0.1)
diff-lcs (>= 1.1.2)
rspec-mocks (2.0.1)
rspec-core (~> 2.0.1)
rspec-expectations (~> 2.0.1)
sys-uname (0.8.4)

PLATFORMS
ruby

DEPENDENCIES
ZenTest (>= 4.4.0)
activemodel (>= 3.0.1)
activesupport (>= 3.0.1)
autotest (>= 4.4.1)
autotest-fsevent (>= 0.2.3)
gitmodel!
grit (>= 2.3.0)
lockfile (>= 1.4.3)
rspec (>= 2.0.1)
20 changes: 20 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Copyright (c) 2009 Paul Dowman

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
109 changes: 109 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
GitModel: distributed, versioned NoSQL for Ruby
---------------------------------------------------

GitModel is an
[ActiveModel](http://yehudakatz.com/2010/01/10/activemodel-make-any-ruby-object-feel-like-activerecord/)-compliant
persistence framework for Ruby that uses [Git](http://git-scm.com/) for
versioning and remote syncing.

GitModel persists Ruby objects using Git as a data storage engine. It's an
ActiveModel implementation so it works stand-alone or in Rails 3 as a drop-in
replacement for ActiveRecord or DataMapper.

Because the database is a Git repository it can be synced across multiple
machines, manipulated with standard Git client tools, can be branched and
merged, and of course keeps the history of all changes.


Status
------

_It is nowhere near production ready but I'm working on it. Please feel free to
contribute tests and/or code to help!_


Why it's awesome
----------------

* Schema-less NoSQL data store
* Each record is a normal Ruby object, attributes are any Ruby type or large
chunks of binary data
* Never lose data, history is kept forever and can be restored simply using
standard Git tools
* Branch and merge your production data
* GitModel can actually work with different branches
* Branch or tag snapshots of your data
* Experiment on production data using branches, for example to test a
migration
* Distributed (synced using standard Git push/pull)
* Transactions
* Metadata for all database changes (Git commit messages, date & time, etc.)
* The database is simply files and directores stored in a Git repository.
GitModel uses the Git repo directly (rather than Git's checked-out "working
copy") but you can do a "git checkout" to view and manipulate the database
contents, and then "git commit"
* Test-driven development and excellent test coverage
* Clean and easy-to-use API


Database file structure
-----------------------

Each type of object is stored in a top-level directory (this is analogous to
ActiveRecord tables), and each object is stored in a subdirectory which is
named using the object's id (i.e. the primary key). Attributes that are Ruby
types (strings, numbers, hashes, arrays, whatever) are stored in a file named
attributes.json and large binary attributes ("blobs") are stored in their own
files.

For example, a database for a blogging app with three Post objects and five
Comment objects might have a directory structure that looks like this:

* db-root
* comments
* 2010-01-03-328
* _attributes.json_
* 2010-05-29-742
* _attributes.json_
* 2010-10-09-934
* _attributes.json_
* 2010-10-12-132
* _attributes.json_
* 2010-10-12-665
* _attributes.json_
* posts
* hotdog-eating-contest
* _attributes.json_
* _hotdogs.jpg_
* _the-aftermath.jpg_
* lessons-learned
* _attributes.json_
* _summary.xls_
* running-with-scissors
* _attributes.json_
* _oops.jpg_
* _speedy.jpg_

In the above example _attributes.json_ holds the attributes which are
represented by Ruby types, and binary data "blobs" are stored in files.

To Do
-----

* Querying
* Use AREL?
* Transactions
* allow blocks to execute within a transaction so multiple changes occur in
one Git commit
* Finish some pending specs
* Associations
* API documentation
* Rails integration
* rake tasks
* generators
* Performance
* Haven't optimized for performance yet.
* Some places where we do blatently stupid things have been marked with
PERFORMANCE comments.


3 changes: 3 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
require 'bundler'
Bundler::GemHelper.install_tasks

1 change: 1 addition & 0 deletions autotest/discover.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Autotest.add_discovery { "rspec2" }
36 changes: 36 additions & 0 deletions gitmodel.gemspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
Gem::Specification.new do |s|
s.name = 'gitmodel'
s.version = '0.0.0'
s.platform = Gem::Platform::RUBY

s.authors = ["Paul Dowman"]
s.email = '[email protected]'
s.homepage = 'http://github.com/pauldowman/gitmodel'

s.summary = %q{An ActiveModel-compliant persistence framework for Ruby that uses Git for versioning and remote syncing.}
s.description = <<-DESC.strip.gsub(/\n\s+/, " ")
GitModel persists Ruby objects using Git as a data storage engine. It's an
ActiveModel implementation so it works stand-alone or in Rails 3 as a drop-in
replacement for ActiveRecord or DataMapper. Because the database is a Git
repository it can be synced across multiple machines, manipulated with standard
Git client tools, can be branched and merged, and of course keeps the history
of all changes.
DESC

s.add_dependency 'activemodel', '>= 3.0.1'
s.add_dependency 'activesupport', '>= 3.0.1'
s.add_dependency 'grit', '>= 2.3.0'
s.add_dependency 'lockfile', '>= 1.4.3'

s.add_development_dependency 'ZenTest', '>= 4.4.0'
s.add_development_dependency 'autotest', '>= 4.4.1'
s.add_development_dependency 'autotest-fsevent', '>= 0.2.3' if RUBY_PLATFORM.downcase.include?("darwin") # OS X only
s.add_development_dependency 'rspec', '>= 2.0.1'

s.files = `git ls-files`.split("\n")
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
s.require_paths = ["lib"]

end

76 changes: 76 additions & 0 deletions lib/gitmodel.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
require 'rubygems'
require 'bundler/setup'

require 'active_model'
require 'active_support/all' # TODO we don't really want all here, clean this up
require 'grit'
require 'json'
require 'lockfile'
require 'pp'

$:.unshift(File.dirname(__FILE__))
require 'gitmodel/errors'
require 'gitmodel/persistable'
require 'gitmodel/transaction'

module GitModel

# db_root must be an existing git repo. (It can be created with create_db!)
# Bare repositories aren't supported yet, it must be a normal git repo with a
# working directory and a '.git' subdirectory.
mattr_accessor :db_root
self.db_root = './gitmodel-data'

mattr_accessor :default_branch
self.default_branch = 'master'

mattr_accessor :logger
self.logger = ::Logger.new(STDERR)
self.logger.level = ::Logger::WARN

mattr_accessor :git_user_name
mattr_accessor :git_user_email

def self.repo
@@repo = Grit::Repo.new(GitModel.db_root)
end

# Create the database defined in db_root. Raises an exception if it exists.
def self.create_db!
raise "Database #{db_root} already exists!" if File.exist? db_root
if db_root =~ /.+\.git/
#logger.info "Creating database (bare): #{db_root}"
#Grit::Repo.init_bare db_root
logger.error "Bare repositories aren't supported yet"
else
logger.info "Creating database: #{db_root}"
Grit::Repo.init db_root
end
end

# Delete and re-create the database defined in db_root. Dangerous!
def self.recreate_db!
logger.info "Deleting database #{db_root}!!"
FileUtils.rm_rf db_root
create_db!
end

def self.last_commit(branch = nil)
branch ||= default_branch
# PERFORMANCE Cache this somewhere and update it on commit?
# (Need separate instance per branch)

return nil unless repo.commits(branch).any?

# We should be able to use just repo.commits(branch).first here but
# this is a workaround for this bug:
# http://github.com/mojombo/grit/issues/issue/38
GitModel.repo.commits("#{branch}^..#{branch}").first || GitModel.repo.commits(branch).first
end

def self.current_tree(branch = nil)
c = last_commit(branch)
c ? c.tree : nil
end

end
25 changes: 25 additions & 0 deletions lib/gitmodel/errors.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module GitModel

# Generic GitModel exception class.
class GitModelError < StandardError
end

# Raised when GitModel cannot find record by given id or set of ids.
class RecordNotFound < GitModelError
end

# Raised by GitModel::Persistable.save! and GitModel::Persistable.create! methods when record cannot be
# saved because record is invalid.
class RecordNotSaved < GitModelError
end

class RecordExists < GitModelError
end

class RecordDoesntExist < GitModelError
end

class NullId < GitModelError
end

end
Loading

0 comments on commit da6f07f

Please sign in to comment.