datapathy 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,7 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
6
+ benchmark.db
7
+ Gemfile.lock
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --colour
2
+ --format progress
3
+ --backtrace
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'http://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in datapathy-ls-adapter.gemspec
4
+ gemspec
5
+
6
+ #gem "resourceful", :path => "../resourceful"
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Paul Sadauskas
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,19 @@
1
+ datapathy
2
+ =========
3
+
4
+ The stupid-simple ORM
5
+
6
+ Roadmap
7
+ =======
8
+
9
+ * 0.1.0 - CRUD API in adapters & models completed
10
+ * 0.2.0 - Lazy Collections
11
+ * 0.3.0 - Validations & Errors
12
+
13
+
14
+
15
+
16
+ Copyright
17
+ =========
18
+
19
+ Copyright (c) 2009 Paul Sadauskas. See LICENSE for details.
@@ -0,0 +1,40 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "datapathy"
8
+ gem.summary = "The stupid-simple ORM"
9
+ gem.email = "psadauskas@gmail.com"
10
+ gem.homepage = "http://github.com/paul/datapathy"
11
+ gem.authors = ["Paul Sadauskas"]
12
+ gem.version = "0.5.0"
13
+
14
+ gem.add_dependency "activesupport", "~> 3.0.0"
15
+ gem.add_dependency "activemodel", "~> 3.0.0"
16
+ gem.add_development_dependency "uuidtools", "~> 2.0"
17
+ gem.add_development_dependency "rspec"
18
+ end
19
+
20
+ rescue LoadError
21
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
22
+ end
23
+
24
+ require 'spec/rake/spectask'
25
+ Spec::Rake::SpecTask.new(:spec) do |spec|
26
+ spec.libs << 'lib' << 'spec'
27
+ spec.spec_files = FileList['spec/**/*_spec.rb']
28
+ spec.spec_opts << ['--options', 'spec/spec.opts']
29
+ end
30
+
31
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
32
+ spec.libs << 'lib' << 'spec'
33
+ spec.pattern = 'spec/**/*_spec.rb'
34
+ spec.rcov = true
35
+ end
36
+
37
+ task :default => :spec
38
+
39
+ task :gem => :build
40
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,67 @@
1
+
2
+ require 'rubygems'
3
+ require 'rbench'
4
+
5
+ $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib"))
6
+ require 'datapathy'
7
+ class DatapathyModel
8
+ include Datapathy::Model
9
+
10
+ persists :a, :b, :c
11
+ end
12
+
13
+ require 'fattr'
14
+ class MyFattr
15
+ fattr :a, :b, :c
16
+
17
+ def initialize(args = {})
18
+ args.each do |k,v|
19
+ send(k, v)
20
+ end
21
+ end
22
+ end
23
+
24
+ class AttrAccessor
25
+ attr_accessor :a, :b, :c
26
+
27
+ def initialize(args = {})
28
+ args.each do |k,v|
29
+ send(:"#{k}=", v)
30
+ end
31
+ end
32
+ end
33
+
34
+ require 'ostruct'
35
+ class MyStruct < OpenStruct; end
36
+
37
+ class MyHash < Hash; end
38
+
39
+ ATTRS = {:a => 1, :b => 2, :c => 3}
40
+
41
+ RBench.run(100_000) do
42
+
43
+ column :datapathy
44
+ column :fattr
45
+ column :accessor
46
+ column :ostruct
47
+ column :hash
48
+ column :object
49
+
50
+ report "empty args" do
51
+ datapathy { DatapathyModel.new }
52
+ fattr { MyFattr.new }
53
+ accessor { AttrAccessor.new }
54
+ ostruct { MyStruct.new }
55
+ hash { Hash.new }
56
+ object { Object.new }
57
+ end
58
+
59
+ report "hash args" do
60
+ datapathy { DatapathyModel.new(ATTRS) }
61
+ fattr { MyFattr.new(ATTRS) }
62
+ accessor { AttrAccessor.new(ATTRS) }
63
+ ostruct { MyStruct.new(ATTRS) }
64
+ hash { Hash.new(ATTRS) }
65
+ end
66
+ end
67
+
@@ -0,0 +1,106 @@
1
+
2
+ require 'rubygems'
3
+ require 'rbench'
4
+
5
+ $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib"))
6
+ require 'datapathy'
7
+ class DatapathyModel
8
+ include Datapathy::Model
9
+
10
+ persists :id, :title, :text
11
+ end
12
+
13
+ require 'dm-core'
14
+ class DataMapperModel
15
+ include DataMapper::Resource
16
+
17
+ property :id, Integer, :key => true
18
+ property :title, String
19
+ property :text, String
20
+ end
21
+ DataMapper.setup(:default, :adapter => :in_memory)
22
+
23
+ require 'active_record'
24
+ ActiveRecord::Base.establish_connection(
25
+ :adapter => "sqlite3",
26
+ :database => "benchmark.db"
27
+ )
28
+ ActiveRecord::Base.connection.execute("DROP TABLE IF EXISTS active_record_models")
29
+ ActiveRecord::Base.connection.execute("CREATE TABLE active_record_models (id INTEGER UNIQUE, title STRING, text STRING)")
30
+ class ActiveRecordModel < ActiveRecord::Base
31
+ end
32
+ # Have AR scan the table before the benchmark
33
+ ActiveRecordModel.new
34
+
35
+ class PlainModel
36
+ attr_accessor :id, :title, :text
37
+
38
+ def initialize(attrs = {})
39
+ @id, @title, @text = attrs[:id], attrs[:title], attrs[:text]
40
+ end
41
+
42
+ end
43
+
44
+ class HashModel
45
+ def initialize(attributes = {})
46
+ attrs = {}
47
+ attrs.merge!(attributes)
48
+ end
49
+ end
50
+
51
+ ATTRS = {:id => 1, :title => "Foo", :text => "Bar"}
52
+
53
+ RBench.run(100_000) do
54
+
55
+ column :times
56
+ column :plain, :title => "Ruby Class"
57
+ column :hash, :title => "Hash Model"
58
+ column :datapathy, :title => "Datapathy #new"
59
+ column :datapathy_a, :title => "Datapathy #new_from_attributes"
60
+ column :datamapper, :title => "DM #{DataMapper::VERSION}"
61
+ column :ar, :title => "AR 3.0.pre"
62
+
63
+ report "#new (no attributes)" do
64
+ plain do
65
+ PlainModel.new
66
+ end
67
+ hash do
68
+ HashModel.new
69
+ end
70
+ datapathy do
71
+ DatapathyModel.new
72
+ end
73
+ datapathy_a do
74
+ DatapathyModel.new_from_attributes
75
+ end
76
+ datamapper do
77
+ DataMapperModel.new
78
+ end
79
+ ar do
80
+ ActiveRecordModel.new
81
+ end
82
+ end
83
+
84
+ report "#new (3 attributes)" do
85
+ plain do
86
+ PlainModel.new ATTRS
87
+ end
88
+ hash do
89
+ HashModel.new ATTRS
90
+ end
91
+ datapathy do
92
+ DatapathyModel.new ATTRS
93
+ end
94
+ datapathy_a do
95
+ DatapathyModel.new_from_attributes ATTRS
96
+ end
97
+ datamapper do
98
+ DataMapperModel.new ATTRS
99
+ end
100
+ ar do
101
+ ActiveRecordModel.new ATTRS
102
+ end
103
+ end
104
+
105
+
106
+ end
@@ -0,0 +1,34 @@
1
+
2
+ require 'rubygems'
3
+ require 'rbench'
4
+
5
+ $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib"))
6
+ require 'datapathy'
7
+ class DatapathyModel
8
+ include Datapathy::Model
9
+
10
+ persists :id, :title, :text
11
+ end
12
+
13
+ HASH = {
14
+ :id => 1,
15
+ :title => "test",
16
+ :text => "Lorem Ipsum"
17
+ }
18
+
19
+
20
+ RBench.run(500_000) do
21
+
22
+ column :hash
23
+ column :model
24
+
25
+ report "init" do
26
+ hash { HASH }
27
+ model { DatapathyModel.new(HASH) }
28
+ end
29
+
30
+ report "search" do
31
+ hash { [HASH].select { |h| h[:title] == "test" } }
32
+ model { [DatapathyModel.new(HASH)].select { |m| m.title == "test" } }
33
+ end
34
+ end
@@ -0,0 +1,31 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{datapathy}
8
+ s.version = "0.6.0"
9
+
10
+ s.authors = ["Paul Sadauskas"]
11
+ s.date = %q{2010-09-01}
12
+ s.email = %q{psadauskas@gmail.com}
13
+ s.homepage = %q{http://github.com/paul/datapathy}
14
+ s.summary = %q{The stupid-simple ORM for HTTP web services}
15
+
16
+ s.require_paths = ["lib"]
17
+
18
+ s.files = `git ls-files`.split("\n")
19
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
20
+
21
+ s.add_runtime_dependency "activesupport", "> 3.1.0"
22
+ s.add_runtime_dependency "activemodel", "> 3.1.0"
23
+ s.add_runtime_dependency "resourceful"
24
+ s.add_runtime_dependency "yajl-ruby"
25
+
26
+ s.add_development_dependency "uuidtools"
27
+ s.add_development_dependency "rspec"
28
+ s.add_development_dependency "artifice"
29
+ s.add_development_dependency "sinatra"
30
+ end
31
+
@@ -0,0 +1,37 @@
1
+
2
+ # only require the parts of activesupport we want
3
+ require 'active_support/inflector'
4
+ require 'active_support/core_ext/string/inflections'
5
+
6
+ module Datapathy
7
+ extend self
8
+
9
+ attr_accessor :services_uri
10
+
11
+ VERSION = "0.6.0"
12
+ def version
13
+ VERSION
14
+ end
15
+
16
+ def adapter
17
+ @adapter ||= Datapathy::Adapters::HttpAdapter.new(:services_uri => services_uri)
18
+ end
19
+
20
+ def configure
21
+ block_given? ? yield(self) : self
22
+ end
23
+
24
+ class RecordNotFound < StandardError
25
+ end
26
+
27
+ end
28
+
29
+ require 'datapathy/log_subscriber'
30
+ require 'datapathy/model'
31
+ require 'datapathy/query'
32
+ require 'datapathy/collection'
33
+ require 'datapathy/adapters/http_adapter'
34
+
35
+ require 'datapathy/models/service'
36
+
37
+ require 'datapathy/railtie' if defined?(Rails)
@@ -0,0 +1,121 @@
1
+ require 'resourceful'
2
+ require 'yajl'
3
+ require 'active_support/core_ext/hash/keys'
4
+
5
+ module Datapathy::Adapters
6
+
7
+ class HttpAdapter < Datapathy::Adapters::AbstractAdapter
8
+
9
+ attr_reader :http, :services_uri
10
+
11
+ def initialize(options = {})
12
+ super
13
+
14
+ @services_uri = @options[:services_uri]
15
+
16
+ @http = Resourceful::HttpAccessor.new
17
+ @http.logger = @options[:logger] if @options[:logger]
18
+ @http.cache_manager = Resourceful::InMemoryCacheManager.new
19
+ #@http.add_authenticator Resourceful::LSAuthenticator.new(@username, @password) # Add a custom authenticator
20
+ end
21
+
22
+ def create(collection)
23
+ query = collection.query
24
+ http_resource = http_resource_for(query)
25
+
26
+ collection.each do |resource|
27
+ record = serialize(resource)
28
+ content_type = "application/vnd.ls.v1+json"
29
+
30
+ begin
31
+ response = http_resource.post(record, "Content-Type" => content_type)
32
+ resource.key = response.header['Location']
33
+ resource.merge!(deserialize(response)) unless response.body.blank?
34
+ rescue Resourceful::UnsuccessfulHttpRequestError => e
35
+ if e.http_response.code == 403
36
+ set_errors(resource, e)
37
+ else
38
+ raise e
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ def read(collection)
45
+ query = collection.query
46
+ if query.key_lookup?
47
+ response = http.resource(query.key, default_headers).get
48
+ Array.wrap(deserialize(response))
49
+ else
50
+ response = http_resource_for(query).get
51
+ records = deserialize(response)[:items]
52
+ records.map! { |r| r.symbolize_keys! }
53
+ records
54
+ end
55
+ end
56
+
57
+ def update(attributes, collection)
58
+ collection.each do |resource|
59
+ content = serialize(resource, attributes)
60
+ content_type = content_type_for(resource)
61
+
62
+ begin
63
+ response = http.resource(resource.href, default_headers).put(content, "Content-Type" => content_type)
64
+ resource.merge!(deserialize(response)) unless response.body.blank?
65
+ rescue Resourceful::UnsuccessfulHttpRequestError => e
66
+ if e.http_response.code == 403
67
+ set_errors(resource, e)
68
+ else
69
+ raise e
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ protected
76
+
77
+ def deserialize(response)
78
+ Yajl::Parser.parse(response.body.gsub('\/', '/')).symbolize_keys
79
+ end
80
+
81
+ def serialize(resource, attrs_for_update = {})
82
+ attrs = resource.persisted_attributes.dup.merge(attrs_for_update)
83
+ attrs.delete_if { |k,v| v.nil? }
84
+ Yajl::Encoder.encode(attrs)
85
+ end
86
+
87
+ def http_resource_for(query)
88
+ model = query.model
89
+
90
+ url = if query.respond_to?(:location) && location = query.location
91
+ location
92
+ elsif model == Service
93
+ services_uri
94
+ else
95
+ service = Service[model.service_type]
96
+ service.href
97
+ end
98
+
99
+ raise "Could not identify a location to look for #{model}" unless url
100
+
101
+ http.resource(url, default_headers)
102
+ end
103
+
104
+ def set_errors(resource, exception)
105
+ errors = deserialize(exception.http_response)[:errors]
106
+ errors.each do |field, messages|
107
+ resource.errors[field].push *messages
108
+ end
109
+ end
110
+
111
+
112
+ def default_headers
113
+ @default_headers ||= {
114
+ :accept => 'application/vnd.ls.v1+json,application/json'
115
+ }
116
+ end
117
+
118
+ end
119
+
120
+ end
121
+