datapathy 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+