datamapper4rail 0.1.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,6 @@
1
+ === 1.0.0 / 2009-03-18
2
+
3
+ * 1 major enhancement
4
+
5
+ * Birthday!
6
+
@@ -0,0 +1,22 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ generators/dm_install/dm_install_generator.rb
6
+ generators/dm_install/templates/datamapper.rake
7
+ generators/dm_migration/dm_migration_generator.rb
8
+ generators/dm_migration/templates/migration.rb
9
+ generators/dm_model/dm_model_generator.rb
10
+ generators/dm_model/templates/model.rb
11
+ generators/dm_model/templates/unit_test.rb
12
+ generators/rspec_default_values.rb
13
+ generators/rspec_dm_model/rspec_dm_model_generator.rb
14
+ generators/rspec_dm_model/templates/model.rb
15
+ generators/rspec_dm_model/templates/model_spec.rb
16
+ lib/datamapper4rails.rb
17
+ lib/datamapper4rails/adapters/base_adapter.rb
18
+ lib/datamapper4rails/adapters/restful_adapter.rb
19
+ lib/datamapper4rails/database_config.rb
20
+ lib/datamapper4rails/datamapper_store.rb
21
+ lib/datamapper4rails/restful_transactions.rb
22
+ lib/datamapper4rails/version.rb
@@ -0,0 +1,101 @@
1
+ = datamapper4rails
2
+
3
+ * http://datamapper4rail.rubyforge.org
4
+
5
+ == DESCRIPTION:
6
+
7
+ collection of datamapper related extensions. mostly needed to run within rails. the restful transactions is around filter for rails. the restful adapter can be outside of rails. datamapper store is a session store for rails which uses datamapper as persistent layer. the generators produces datamapper models for your rails application. quite a few things are "stolen" from dm-more/rails_datamapper. a lot of things do not work there and patches are still in process to be applied so until dm-more/rails_datamapper catches up, ut I hope these two project merge someday again.
8
+
9
+ == FEATURES/PROBLEMS:
10
+
11
+ * restful adapter does handle associations partially and does not handle collections
12
+
13
+ == SYNOPSIS:
14
+
15
+ === restful transaction
16
+
17
+ wraps all modifying requests (POST, PUT, DELETE) into a transaction. any error/exception as well render of a page will rollback the transaction. typically if a POST, PUT or DELETE succeeds then a redirect gets send back to browser.
18
+
19
+ credits of the main idea goes to http://www.redhillonrails.org
20
+
21
+ is implemented as around filter in rails and gets prepend to the filters of the action_controller of rails
22
+
23
+ require 'datamapper4rails/restful_transations
24
+
25
+ === datamapper session store
26
+
27
+ this is just the datamapper compaign to the activerecord_store.
28
+
29
+ in config/environment.rb add
30
+
31
+ config.action_controller.session_store = :datamapper_store
32
+
33
+ in case you need a memory cache for your sessions add this
34
+
35
+ config.action_controller.session = { :cache => true }
36
+
37
+ === generators for datamapper models
38
+
39
+ this is taken from dm-more/rails_datamapper and extended it - to my liking. patches are submitted upstream until they get applied this will remain here.
40
+
41
+ * dm-install: rake task for datamapper: automigrate, autoupgrade
42
+ * dm_model
43
+ * rspec_dm_model
44
+
45
+ === database config
46
+
47
+ this is also taken from dm-more/rails_datamapper and just uses the
48
+ 'config/database.yml' to configure a database connection. it also allows to configure multiple repositories for a single environment just nest the config in such a way:
49
+
50
+ development:
51
+ repositories:
52
+ default:
53
+ adapter: sqlite3
54
+ database: db/development.sqlite3
55
+ pool: 5
56
+ timeout: 5000
57
+ users:
58
+ adapter: ldap
59
+ host: localhost
60
+ port: 389
61
+ base: dc=example,dc=com
62
+ bind_name: cn=admin,dc=example,dc=com
63
+ password: secret
64
+
65
+
66
+ === restful adapter
67
+
68
+ this comes partly from dm-ldap-adapter and partly from dm-more/adapter/rest_adapter. the restful adapter allows to Create, Retrieve, Update and Delete resources via a restful service.
69
+
70
+ == REQUIREMENTS:
71
+
72
+ * datamapper
73
+
74
+ == INSTALL:
75
+
76
+ * sudo gem install datamapper4rails
77
+
78
+ == LICENSE:
79
+
80
+ (The MIT License)
81
+
82
+ Copyright (c) 2009 Kristian Meier
83
+
84
+ Permission is hereby granted, free of charge, to any person obtaining
85
+ a copy of this software and associated documentation files (the
86
+ 'Software'), to deal in the Software without restriction, including
87
+ without limitation the rights to use, copy, modify, merge, publish,
88
+ distribute, sublicense, and/or sell copies of the Software, and to
89
+ permit persons to whom the Software is furnished to do so, subject to
90
+ the following conditions:
91
+
92
+ The above copyright notice and this permission notice shall be
93
+ included in all copies or substantial portions of the Software.
94
+
95
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
96
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
97
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
98
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
99
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
100
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
101
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,32 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+ require './lib/datamapper4rails/version.rb'
6
+
7
+ Hoe.new('datamapper4rail', Datamapper4rails::VERSION) do |p|
8
+ p.rubyforge_name = 'datamapper4rail'
9
+ p.developer('mkristian', 'm.kristian@web.de')
10
+ p.extra_deps = ['slf4r']
11
+ p.remote_rdoc_dir = '' # Release to root
12
+ end
13
+
14
+ desc 'Install the package as a gem.'
15
+ task :install => [:clean, :package] do
16
+ gem = Dir['pkg/*.gem'].first
17
+ sh "gem install --local #{gem} --no-ri --no-rdoc"
18
+ end
19
+
20
+ # desc 'Run specifications'
21
+ # Spec::Rake::SpecTask.new(:spec) do |t|
22
+ # if File.exists?('spec/spec.opts')
23
+ # t.spec_opts << '--options' << 'spec/spec.opts'
24
+ # end
25
+ # t.spec_files = Pathname.glob('./spec/**/*_spec.rb')
26
+ # end
27
+
28
+ # require 'yard'
29
+
30
+ # YARD::Rake::YardocTask.new
31
+
32
+ # vim: syntax=Ruby
@@ -0,0 +1,12 @@
1
+ require 'rails_generator/base'
2
+
3
+ class DmInstallGenerator < Rails::Generator::Base
4
+
5
+ def manifest
6
+ record do |m|
7
+ m.directory "lib/tasks"
8
+ m.template "datamapper.rake", "lib/tasks/datamapper.rake"
9
+ end
10
+ end
11
+
12
+ end
@@ -0,0 +1,42 @@
1
+ namespace :db do
2
+
3
+ desc "Perform automigration"
4
+ task :automigrate => :environment do
5
+ FileList["app/models/**/*.rb"].each do |model|
6
+ load model
7
+ end
8
+ ::DataMapper.auto_migrate!
9
+ end
10
+
11
+ desc "Perform non destructive automigration"
12
+ task :autoupgrade => :environment do
13
+ FileList["app/models/**/*.rb"].each do |model|
14
+ load model
15
+ end
16
+ ::DataMapper.auto_upgrade!
17
+ end
18
+
19
+ namespace :migrate do
20
+ task :load => :environment do
21
+ gem 'dm-migrations'
22
+ FileList["db/migrations/*.rb"].each do |migration|
23
+ load migration
24
+ end
25
+ end
26
+
27
+ desc "Migrate up using migrations"
28
+ task :up, :version, :needs => :load do |t, args|
29
+ version = args[:version]
30
+ migrate_up!(version)
31
+ end
32
+
33
+ desc "Migrate down using migrations"
34
+ task :down, :version, :needs => :load do |t, args|
35
+ version = args[:version]
36
+ migrate_down!(version)
37
+ end
38
+ end
39
+
40
+ desc "Migrate the database to the latest version"
41
+ task :migrate => 'db:migrate:up'
42
+ end
@@ -0,0 +1,10 @@
1
+ require 'rails_generator/generators/components/migration/migration_generator'
2
+
3
+ class DmMigrationGenerator < Rails::Generator::NamedBase
4
+
5
+ def manifest
6
+ record do |m|
7
+ m.migration_template "migration.rb", "db/migrate"
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,16 @@
1
+ class <%= migration_name %> < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :<%= table_name %> do |t|
4
+ <% for attribute in attributes -%>
5
+ t.<%= attribute.type %> :<%= attribute.name %>
6
+ <% end -%>
7
+ <% unless options[:skip_timestamps] %>
8
+ t.timestamps
9
+ <% end -%>
10
+ end
11
+ end
12
+
13
+ def self.down
14
+ drop_table :<%= table_name %>
15
+ end
16
+ end
@@ -0,0 +1,27 @@
1
+ require 'rails_generator/generators/components/model/model_generator'
2
+ require 'active_record'
3
+
4
+ class DmModelGenerator <ModelGenerator
5
+
6
+ def manifest
7
+ record do |m|
8
+ # Check for class naming collisions.
9
+ m.class_collisions class_path, class_name, "#{class_name}Test"
10
+
11
+ # Model, test, and fixture directories.
12
+ m.directory File.join('app/models', class_path)
13
+ m.directory File.join('test/unit', class_path)
14
+
15
+ # Model class, unit test, and fixtures.
16
+ m.template 'model.rb', File.join('app/models', class_path, "#{file_name}.rb")
17
+ m.template 'unit_test.rb', File.join('test/unit', class_path, "#{file_name}_test.rb")
18
+
19
+ unless options[:skip_migration]
20
+ m.migration_template 'model:migration.rb', 'db/migrate', :assigns => {
21
+ :migration_name => "Create#{class_name.pluralize.gsub(/::/, '')}"
22
+ }, :migration_file_name => "create_#{file_path.gsub(/\//, '_').pluralize}"
23
+ end
24
+ end
25
+ end
26
+
27
+ end
@@ -0,0 +1,14 @@
1
+ class <%= class_name %>
2
+ include DataMapper::Resource
3
+ property :id, Serial
4
+
5
+ <% for attribute in attributes -%>
6
+ property :<%= attribute.name %>, <%= attribute.type.to_s.capitalize %>, :nullable => false
7
+
8
+ <% end -%>
9
+ <% unless options[:skip_timestamps] %>
10
+ property :created_at, DateTime, :nullable => false
11
+ property :updated_at, DateTime, :nullable => false
12
+
13
+ <% end -%>
14
+ end
@@ -0,0 +1,8 @@
1
+ require 'test_helper'
2
+
3
+ class <%= class_name %>Test < ActiveSupport::TestCase
4
+ # Replace this with your real tests.
5
+ def test_truth
6
+ assert true
7
+ end
8
+ end
@@ -0,0 +1,20 @@
1
+ module Rails
2
+ module Generator
3
+ class GeneratedAttribute
4
+ def default_value
5
+ @default_value ||= case type
6
+ when :int, :integer then "1"
7
+ when :float then "1.5"
8
+ when :decimal, :big_decimal then "9.99"
9
+ when :date_time, :datetime,
10
+ :timestamp, :time then "Time.now"
11
+ when :date then "Date.today"
12
+ when :string, :text then "\"value for #{@name}\""
13
+ when :boolean then "false"
14
+ else
15
+ ""
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,33 @@
1
+ require 'rails_generator/generators/components/model/model_generator'
2
+ require 'active_record'
3
+ require File.dirname(__FILE__) + '/../rspec_default_values'
4
+
5
+ class RspecDmModelExtendedGenerator <ModelGenerator
6
+
7
+ def manifest
8
+ record do |m|
9
+
10
+ # Check for class naming collisions.
11
+ m.class_collisions class_path, class_name
12
+
13
+ # Model, spec, and fixture directories.
14
+ m.directory File.join('app/models', class_path)
15
+ m.directory File.join('spec/models', class_path)
16
+
17
+ # Model class, spec and fixtures.
18
+ m.template 'model.rb', File.join('app/models',
19
+ class_path,
20
+ "#{file_name}.rb")
21
+ m.template 'model_spec.rb', File.join('spec/models',
22
+ class_path,
23
+ "#{file_name}_spec.rb")
24
+ end
25
+
26
+ # unless options[:skip_migration]
27
+ # m.migration_template 'model:migration.rb', 'db/migrate', :assigns => {
28
+ # :migration_name => "Create#{class_name.pluralize.gsub(/::/, '')}"
29
+ # }, :migration_file_name => "create_#{file_path.gsub(/\//, '_').pluralize}"
30
+ # end
31
+ end
32
+
33
+ end
@@ -0,0 +1,16 @@
1
+ class <%= class_name %>
2
+ include DataMapper::Resource
3
+ property :id, Serial
4
+
5
+ <% for attribute in attributes -%>
6
+ property :<%= attribute.name %>, <%= attribute.type.to_s.camelize %>, :nullable => false <% if attribute.type == :string or attribute.type == :text -%>, :format => /^[^<'&">]*$/<% if attribute.type == :string %>, :length => 50<% end -%>
7
+ <% else -%>
8
+
9
+ <% end -%>
10
+ <% end -%>
11
+ <% unless options[:skip_timestamps] %>
12
+ property :created_at, DateTime, :nullable => false
13
+ property :updated_at, DateTime, :nullable => false
14
+
15
+ <% end -%>
16
+ end
@@ -0,0 +1,41 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '<%= '/..' * class_nesting_depth %>/../spec_helper')
2
+
3
+ describe <%= class_name %> do
4
+ before(:each) do
5
+ @valid_attributes = {
6
+ <% attributes.each_with_index do |attribute, attribute_index| -%>
7
+ :<%= attribute.name %> => <%= attribute.default_value %><%= attribute_index == attributes.length - 1 ? '' : ','%>
8
+ <% end -%>
9
+ }
10
+ end
11
+
12
+ <% attributes.each do |attribute| -%>
13
+ it "should require <%= attribute.name %>" do
14
+ <%= singular_name %> = <%= class_name %>.create(@valid_attributes.merge(:<%= attribute.name %> => nil))
15
+ <%= singular_name %>.errors.on(:<%= attribute.name %>).should_not == nil
16
+ end
17
+
18
+ <% if attribute.type == :string or attribute.type == :text -%>
19
+ it 'should not match <%= attribute.name %>' do
20
+ <%= singular_name %> = <%= class_name %>.create(@valid_attributes.merge(:<%= attribute.name %> => "<script" ))
21
+ <%= singular_name %>.errors.on(:<%= attribute.name %>).should_not == nil
22
+ <%= singular_name %> = <%= class_name %>.create(@valid_attributes.merge(:<%= attribute.name %> => "sc'ript" ))
23
+ <%= singular_name %>.errors.on(:<%= attribute.name %>).should_not == nil
24
+ <%= singular_name %> = <%= class_name %>.create(@valid_attributes.merge(:<%= attribute.name %> => "scr&ipt" ))
25
+ <%= singular_name %>.errors.on(:<%= attribute.name %>).should_not == nil
26
+ <%= singular_name %> = <%= class_name %>.create(@valid_attributes.merge(:<%= attribute.name %> => 'scr"ipt' ))
27
+ <%= singular_name %>.errors.on(:<%= attribute.name %>).should_not == nil
28
+ <%= singular_name %> = <%= class_name %>.create(@valid_attributes.merge(:<%= attribute.name %> => "script>" ))
29
+ <%= singular_name %>.errors.on(:<%= attribute.name %>).should_not == nil
30
+ end
31
+
32
+ <% elsif [:integer, :big_decimal, :float].member? attribute.type %>
33
+ it "should be numerical <%= attribute.name %>" do
34
+ <%= singular_name %> = <%= class_name %>.create(@valid_attributes.merge(:<%= attribute.name %> => "none-numberic" ))
35
+ <%= singular_name %>.<%= attribute.name %>.to_i.should == 0
36
+ <%= singular_name %>.errors.size.should == 0
37
+ end
38
+
39
+ <% end -%>
40
+ <% end -%>
41
+ end
@@ -0,0 +1,53 @@
1
+ require 'dm-core'
2
+ def config_file()
3
+ RAILS_ROOT + "/config/database.yml"
4
+ end
5
+
6
+ def create_connection()
7
+ conf = config.dup
8
+ if repositories = conf.delete(:repositories)
9
+ repositories.each do |repo, conf|
10
+ ::DataMapper.setup(repo, conf) unless conf.empty?
11
+ end
12
+ else
13
+ ::DataMapper.setup(:default, conf) unless conf.empty?
14
+ end
15
+ end
16
+
17
+ def get_config_for_environment
18
+ if hash = full_config[RAILS_ENV]
19
+ symbolize_keys(hash)
20
+ elsif hash = full_config[RAILS_ENV.to_sym]
21
+ hash
22
+ else
23
+ raise ArgumentError, "missing environment '#{RAILS_ENV}' in config file #{config_file}"
24
+ end
25
+ end
26
+
27
+ def full_config
28
+ @full_config ||= YAML::load(ERB.new(IO.read(config_file)).result)
29
+ end
30
+
31
+ def config
32
+ @config ||= get_config_for_environment
33
+ end
34
+
35
+ def symbolize_keys(h)
36
+ config = {}
37
+
38
+ h.each do |k, v|
39
+ if k == 'port'
40
+ config[k.to_sym] = v.to_i
41
+ elsif k == 'adapter' && v == 'postgresql'
42
+ config[k.to_sym] = 'postgres'
43
+ elsif v.is_a?(Hash)
44
+ config[k.to_sym] = symbolize_keys(v)
45
+ else
46
+ config[k.to_sym] = v
47
+ end
48
+ end
49
+
50
+ config
51
+ end
52
+
53
+ create_connection()
@@ -0,0 +1,198 @@
1
+ require 'dm-core'
2
+ require 'slf4r'
3
+
4
+ module DataMapper
5
+ module Adapters
6
+ class NoopTransaction
7
+
8
+ def close ; end
9
+ def begin ; end
10
+ def prepare ; end
11
+ def commit ; end
12
+ def rollback ; end
13
+ def rollback_prepared ; end
14
+
15
+ end
16
+ class BaseAdapter < AbstractAdapter
17
+
18
+ include Slf4r::Logger
19
+
20
+ # @see AbstractAdapter
21
+ def transaction_primitive
22
+ NoopTransaction.new
23
+ end
24
+
25
+ def initialize(name, uri_or_options)
26
+ super(name, uri_or_options)
27
+ end
28
+
29
+ protected
30
+
31
+ # checks whether a given resource fullfils the conditions
32
+ # @param [DataMapper::Resource] resource
33
+ # @param [Array<Condition>] conditions
34
+ # @return [Boolean]
35
+ # true if the resource are within the conditions otherwise false
36
+ def filter_resource(resource, conditions)
37
+ #puts "condi"
38
+ #p conditions
39
+ # no conditation => no filter
40
+ if conditions.size == 0
41
+ true
42
+ else
43
+ conditions.all? do |tuple|
44
+ operator, property, bind_value = *tuple
45
+
46
+ value = property.get!(resource)
47
+ case operator
48
+ when :eql, :in then equality_comparison(bind_value, value)
49
+ when :not then !equality_comparison(bind_value, value)
50
+ when :like then Regexp.new(bind_value.gsub(/%/, ".*")) =~ value
51
+ when :gt then !value.nil? && value > bind_value
52
+ when :gte then !value.nil? && value >= bind_value
53
+ when :lt then !value.nil? && value < bind_value
54
+ when :lte then !value.nil? && value <= bind_value
55
+ else raise "Invalid query operator: #{operator.inspect}"
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ # helper method to dispatch the equality test for different
62
+ # classes
63
+ def equality_comparison(bind_value, value)
64
+ case bind_value
65
+ when Array, Range then bind_value.include?(value)
66
+ when NilClass then value.nil?
67
+ else bind_value == value
68
+ end
69
+ end
70
+
71
+ public
72
+
73
+ # @see AbstractAdapter
74
+ # @param [Array<DataMapper::Resources>] resources
75
+ # aaaa
76
+ # @return [Fixnum]
77
+ # number of the newly created resources
78
+ def create(resources)
79
+ resources.select do |resource|
80
+
81
+ create_resource(resource)
82
+
83
+ end.size # just return the number of create resources
84
+ end
85
+
86
+ # @see AbstractAdapter
87
+ # @param [Hash] attributes
88
+ # collection of attribute, i.e. the name/value pairs which
89
+ # needs to be updated
90
+ # @param [Query]
91
+ # on all resources which are selected by that query the
92
+ # update will be applied
93
+ # @return [Fixnum]
94
+ # number of the updated resources
95
+ def update(attributes, query)
96
+ read_many(query).select do |resource|
97
+
98
+ update_resource(resource, attributes)
99
+
100
+ end.size
101
+ end
102
+
103
+ # @see AbstractAdapter
104
+ # @param [DataMapper::Query] query
105
+ # which selects the resource
106
+ # @return [DataMapper::Resource]
107
+ # the found Resource or nil
108
+ def read_one(query)
109
+ result = read_resource(query)
110
+ if result.is_a? Resource
111
+ result
112
+ elsif result # assume result to be Array with the values
113
+ #puts "------------------"
114
+ #p result
115
+ query.model.load(result, query)
116
+ end
117
+ end
118
+
119
+ # @see AbstractAdapter
120
+ # @param [DataMapper::Query] query
121
+ # which selects the resources
122
+ # @return [DataMapper::Collection]
123
+ # collection of Resources
124
+ def read_many(query)
125
+ Collection.new(query) do |set|
126
+ result = read_resources(query)
127
+ #puts "read_many"
128
+ #p result
129
+ if result.size > 0 and result.first.is_a? Resource
130
+ set.replace(result)
131
+ else
132
+ result.each do |values|
133
+ set.load(values)
134
+ end
135
+ end
136
+ end
137
+ end
138
+
139
+ # @see AbstractAdapter
140
+ # @param [Query] query
141
+ # which selects the resources to be deleted
142
+ # @return [Fixnum]
143
+ # number of the deleted resources
144
+ def delete(query)
145
+ read_many(query).each do |resource|
146
+
147
+ delete_resource(resource)
148
+
149
+ end.size
150
+ end
151
+
152
+ private
153
+
154
+ # @param [DataMapper::Resource] resource
155
+ # which will be created
156
+ # @return [DataMapper::Resource]
157
+ # either the resource itself if the creation was successful or nil
158
+ def create_resource(resource)
159
+ raise NotImplementedError.new
160
+ end
161
+
162
+ # @param [DataMapper::Query] query
163
+ # which selects the resource
164
+ # @return [DataMapper::Resource,Array<String>]
165
+ # the resource or a set of values ordered in the same manner as query.fields attributes
166
+ def read_resource(query)
167
+ raise NotImplementedError.new
168
+ end
169
+
170
+ # @param [DataMapper::Query] query
171
+ # which selects the resources
172
+ # @return [Array<DataMapper::Resource>,Array<String>]
173
+ # resources or ordered values
174
+ # @see #read_resource
175
+ def read_resources(query)
176
+ raise NotImplementedError.new
177
+ end
178
+
179
+ # @param [DataMapper::Resource] resource
180
+ # which will be updated with the given attributes.
181
+ # @param [Hash] attributes
182
+ # the keys are the property names and the values are the new values of that property.
183
+ # @return [DataMapper::Resource]
184
+ # resource on success otherwise nil
185
+ def update_resource(resource, attributes)
186
+ raise NotImplementedError.new
187
+ end
188
+
189
+ # @param [DataMapper::Resource] resource
190
+ # which will be deleted
191
+ # @return [DataMapper::Resource]
192
+ # either the resource if the deletion was successful or nil
193
+ def delete_resource(resource)
194
+ raise NotImplementedError.new
195
+ end
196
+ end
197
+ end
198
+ end
@@ -0,0 +1,252 @@
1
+ require 'adapters/restful_adapter'
2
+ require 'net/http'
3
+ require 'extlib/inflection'
4
+ require 'extlib/module'
5
+
6
+ module DataMapper
7
+ module Adapters
8
+ class RestfulAdapter < BaseAdapter
9
+
10
+ include ::Slf4r::Logger
11
+
12
+ def resource_name_from_model(model)
13
+ ::Extlib::Inflection.underscore(model.name)
14
+ end
15
+
16
+ def resource_name_from_query(query)
17
+ resource_name_from_model(query.model)
18
+ end
19
+
20
+ def keys_from_query(query)
21
+ keys = query.model.key
22
+ # work around strange missing of properties in model
23
+ # but the query has still the fields :P
24
+ if keys.size == 0
25
+ query.fields.select do |f|
26
+ f.key?
27
+ end
28
+ else
29
+ keys
30
+ end
31
+ end
32
+
33
+ def key_value_from_query(query)
34
+ keys = keys_from_query(query)
35
+ logger.debug { "keys=#{keys.inspect}" }
36
+ if keys.size == 1
37
+ key = keys[0]
38
+ # return the third element of the condition array
39
+ # which belongs to the key
40
+ query.conditions.detect do |c|
41
+ c[1] == key
42
+ end[2]
43
+ else
44
+ raise "compound keys are not supported"
45
+ end
46
+ end
47
+
48
+ def http_get(uri)
49
+ send_request do |http|
50
+ request = Net::HTTP::Get.new(uri)
51
+ request.basic_auth(@uri[:login],
52
+ @uri[:password]) unless @uri[:login].blank?
53
+ http.request(request)
54
+ end
55
+ end
56
+
57
+ def http_post(uri, data = nil)
58
+ send_request do |http|
59
+ request = Net::HTTP::Post.new(uri, {
60
+ 'content-type' => 'application/xml',
61
+ 'content-length' => data.length.to_s
62
+ })
63
+ request.basic_auth(@uri[:login],
64
+ @uri[:password]) unless @uri[:login].blank?
65
+ http.request(request, data)
66
+ end
67
+ end
68
+
69
+ def http_put(uri, data = {})
70
+ send_request do |http|
71
+ request = Net::HTTP::Put.new(uri)
72
+ request.basic_auth(@uri[:login],
73
+ @uri[:password]) unless @uri[:login].blank?
74
+ request.set_form_data(data)
75
+ http.request(request)
76
+ end
77
+ end
78
+
79
+ def http_delete(uri)
80
+ send_request do |http|
81
+ request = Net::HTTP::Delete.new(uri)
82
+ request.basic_auth(@uri[:login],
83
+ @uri[:password]) unless @uri[:login].blank?
84
+ http.request(request)
85
+ end
86
+ end
87
+
88
+ def send_request(&block)
89
+ res = nil
90
+ Net::HTTP.start(@uri[:host], @uri[:port].to_i) do |http|
91
+ res = yield(http)
92
+ end
93
+ logger.debug { "response=" + res.code }
94
+ res
95
+ end
96
+
97
+ def parse_resource(xml, model, query = nil)
98
+ elements = {}
99
+ associations = {}
100
+ many_to_many = {}
101
+ xml.elements.collect do |element|
102
+ if element.text.nil?
103
+ if element.attributes['type'] == 'array'
104
+ many_to_many[element.name.gsub('-','_').to_sym] = element
105
+ else
106
+ associations[element.name.gsub('-','_').to_sym] = element
107
+ end
108
+ else
109
+ elements[element.name.gsub('-','_').to_sym] = element.text
110
+ end
111
+ end
112
+ #puts
113
+ #puts "elements"
114
+ #p elements
115
+ resource = model.load(model.properties.collect do |f|
116
+ elements[f.name]
117
+ end, query)
118
+ resource.send("#{keys_from_query(query)[0].name}=".to_sym, elements[keys_from_query(query)[0].name] )
119
+ #p resource
120
+ associations.each do |name, association|
121
+ model =
122
+ if rel = model.relationships[name]
123
+ if rel.child_model == model
124
+ rel.parent_model
125
+ else
126
+ rel.child_model
127
+ end
128
+ # else
129
+ #::Extlib::Inflection.constantize(::Extlib::Inflection.classify(name))
130
+ # model.find_const(::Extlib::Inflection.classify(name))
131
+ end
132
+ if resource.respond_to? "#{name}=".to_sym
133
+ resource.send("#{name}=".to_sym,
134
+ parse_resource(association, model,
135
+ ::DataMapper::Query.new(query.repository, model )))
136
+ else
137
+ resource.send(("#{name.to_s.pluralize}<" + "<").to_sym,
138
+ parse_resource(association, model,
139
+ ::DataMapper::Query.new(query.repository, model )))
140
+ end
141
+ end
142
+ resource.instance_variable_set(:@new_record, false)
143
+ resource
144
+ end
145
+
146
+ # @see BaseAdapter
147
+ def create_resource(resource)
148
+ name = resource.model.name
149
+ uri = "/#{name.pluralize}.xml"
150
+ logger.debug { "post #{uri}" }
151
+ response = http_post(uri, resource.to_xml )
152
+ resource_new = parse_resource(REXML::Document::new(response.body).root,
153
+ resource.model,
154
+ ::DataMapper::Query.new(resource.repository,
155
+ resource.model ))
156
+
157
+ # copy all attributes/associations from the downloaded resource
158
+ # to the given resource
159
+ # TODO better pass the given resource into parse_resource
160
+ resource_new.attributes.each do |key, value|
161
+ resource.send(:properties)[key].set!(resource, value)
162
+ end
163
+ resource_new.send(:relationships).each do |key, value|
164
+ resource.send("#{key}=".to_sym, resource_new.send(key))
165
+ end
166
+ resource
167
+ end
168
+
169
+ # @see BaseAdapter
170
+ def read_resource(query)
171
+ if(query.conditions.empty?)
172
+ raise "not implemented"
173
+ else
174
+ key = key_value_from_query(query)
175
+ uri = "/#{resource_name_from_query(query).pluralize}/#{key}.xml"
176
+ logger.debug { "get #{uri}" }
177
+ response = http_get(uri)
178
+ if response.kind_of?(Net::HTTPSuccess)
179
+ parse_resource(REXML::Document::new(response.body).root,
180
+ query.model,
181
+ query)
182
+ else
183
+ #TODO may act on different response codes differently
184
+ end
185
+ end
186
+ end
187
+
188
+ # @see BaseAdapter
189
+ def read_resources(query)
190
+ # raise "not implemented"
191
+ [read_resource(query)]
192
+ end
193
+
194
+ # @overwrite BaseAdapter
195
+ def update(attributes, query)
196
+ name = resource_name_from_query(query)
197
+ params = {}
198
+ attributes.each do |attr, val|
199
+ params["#{name}[#{attr.name}]"]=val
200
+ end
201
+ key = key_value_from_query(query)
202
+ uri = "/#{name.pluralize}/#{key}.xml"
203
+ logger.debug { "put #{uri}" }
204
+ response = http_put(uri, params)
205
+ response.kind_of?(Net::HTTPSuccess)
206
+ end
207
+
208
+ # @see BaseAdapter
209
+ def update_resource(resource, attributes)
210
+ query = resource.to_query
211
+ if(query.conditions.empty?)
212
+ raise "not implemented"
213
+ else
214
+ name = resource.name
215
+ params = {}
216
+ attributes.each do |attr, val|
217
+ params["#{name}[#{attr.name}]"]=val
218
+ end
219
+ key = key_value_from_query(query)
220
+ logger.debug {resource.to_xml}
221
+ response = http_put("/#{resource_name_from_query(query).pluralize}/#{key}.xml", params)
222
+ response.kind_of?(Net::HTTPSuccess)
223
+ end
224
+ end
225
+
226
+ # @overwrite BaseAdapter
227
+ def delete(query)
228
+ # TODO limit == 1 is NOT sufficient ONLY necessary
229
+ if query.limit == 1
230
+ name = resource_name_from_query(query)
231
+ key = key_value_from_query(query)
232
+ uri = "/#{name.pluralize}/#{key}.xml"
233
+ logger.debug { "delete #{uri}" }
234
+ response = http_delete(uri)
235
+ response.kind_of?(Net::HTTPSuccess)
236
+ else
237
+ super
238
+ end
239
+ end
240
+
241
+ # @see BaseAdapter
242
+ def delete_resource(resource)
243
+ name = resource.name
244
+ key = key_value_from_query(resource.to_queryquery)
245
+ uri = "/#{name.pluralize}/#{key}.xml"
246
+ logger.debug { "delete #{uri}" }
247
+ response = http_delete(uri)
248
+ response.kind_of?(Net::HTTPSuccess)
249
+ end
250
+ end
251
+ end
252
+ end
@@ -0,0 +1,49 @@
1
+ require 'dm-core'
2
+
3
+ def config_file()
4
+ RAILS_ROOT + "/config/database.yml"
5
+ end
6
+
7
+ def create_connection()
8
+ conf = config.dup
9
+ repositories = conf.delete(:repositories)
10
+ ::DataMapper.setup(:default, conf) unless conf.empty?
11
+ end
12
+
13
+ def get_config_for_environment
14
+ if hash = full_config[RAILS_ENV]
15
+ symbolize_keys(hash)
16
+ elsif hash = full_config[RAILS_ENV.to_sym]
17
+ hash
18
+ else
19
+ raise ArgumentError, "missing environment '#{RAILS_ENV}' in config file #{config_file}"
20
+ end
21
+ end
22
+
23
+ def full_config
24
+ @full_config ||= YAML::load(ERB.new(IO.read(config_file)).result)
25
+ end
26
+
27
+ def config
28
+ @config ||= get_config_for_environment
29
+ end
30
+
31
+ def symbolize_keys(h)
32
+ config = {}
33
+
34
+ h.each do |k, v|
35
+ if k == 'port'
36
+ config[k.to_sym] = v.to_i
37
+ elsif k == 'adapter' && v == 'postgresql'
38
+ config[k.to_sym] = 'postgres'
39
+ elsif v.is_a?(Hash)
40
+ config[k.to_sym] = symbolize_keys(v)
41
+ else
42
+ config[k.to_sym] = v
43
+ end
44
+ end
45
+
46
+ config
47
+ end
48
+
49
+ create_connection()
@@ -0,0 +1,67 @@
1
+ require 'dm-core'
2
+
3
+ module ActionController
4
+ module Session
5
+ class DatamapperStore < AbstractStore
6
+
7
+ def initialize(app, options = {})
8
+ super
9
+ if options.delete(:cache)
10
+ @@cache = {}
11
+ end
12
+ @@session_class = ::DatamapperStore::Session
13
+ end
14
+
15
+ private
16
+ def get_session(env, sid)
17
+ sid ||= generate_sid
18
+ session =
19
+ if @@cache
20
+ @@cache[sid] || @@session_class.get(sid)
21
+ else
22
+ @@session_class.get(sid)
23
+ end
24
+ [sid, session.nil? ? {} : session.data]
25
+ end
26
+
27
+ def set_session(env, sid, session_data)
28
+ session =
29
+ if @@cache
30
+ @@cache[sid] || @@session_class.get(sid)
31
+ else
32
+ @@session_class.get(sid)
33
+ end || @@session_class.new(:session_id => sid)
34
+ session.data = session_data || {}
35
+ if session.new_record?
36
+ session.updated_at = Time.now
37
+ @@cache[sid] = session if @@cache
38
+ end
39
+ session.save
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ module DatamapperStore
46
+ class Session
47
+
48
+ include ::DataMapper::Resource
49
+
50
+ def self.name
51
+ "session"
52
+ end
53
+
54
+ property :session_id, String, :key => true
55
+
56
+ property :data, Text, :nullable => false, :default => ::Base64.encode64(Marshal.dump({}))
57
+
58
+ property :updated_at, DateTime, :nullable => true, :index => true
59
+
60
+ def data=(data)
61
+ attribute_set(:data, ::Base64.encode64(Marshal.dump(data)))
62
+ end
63
+ def data
64
+ Marshal.load(::Base64.decode64(attribute_get(:data)))
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,42 @@
1
+ module Datamapper4rails
2
+ module RestfulTransactions
3
+
4
+ class Rollback < StandardError
5
+ end
6
+
7
+ module Base
8
+ def self.included(base)
9
+ base.prepend_around_filter(TransactionFilter)
10
+ end
11
+ end
12
+
13
+ class TransactionFilter
14
+ def self.filter(controller)
15
+ case controller.request.method
16
+ when :post, :put, :delete then
17
+ begin
18
+ DataMapper::Transaction.new(DataMapper.repository(:default)) do |*block_args|
19
+ if block_given?
20
+ yield (*block_args)
21
+ # added rollback for all actions which just render
22
+ # a page with validation errors and do not redirect to new idem potent
23
+ # page (http-method get is idem potent within the
24
+ # restful paradigma
25
+ unless controller.response.redirected_to
26
+ raise Datamapper4rails::RestfulTransactions::Rollback
27
+ end
28
+ end
29
+ end
30
+ rescue Datamapper4rails::RestfulTransactions::Rollback
31
+ # ignore,
32
+ # this is just needed to trigger the rollback on the transaction
33
+ end
34
+ else
35
+ yield if block_given?
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ ::ActionController::Base.send(:include, Datamapper4rails::RestfulTransactions::Base)
@@ -0,0 +1,3 @@
1
+ class Datamapper4rails
2
+ VERSION = '0.1.0'
3
+ end
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: datamapper4rail
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - mkristian
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-03-21 00:00:00 +05:30
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: slf4r
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: hoe
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.8.3
34
+ version:
35
+ description: collection of datamapper related extensions. mostly needed to run within rails. the restful transactions is around filter for rails. the restful adapter can be outside of rails. datamapper store is a session store for rails which uses datamapper as persistent layer. the generators produces datamapper models for your rails application. quite a few things are "stolen" from dm-more/rails_datamapper. a lot of things do not work there and patches are still in process to be applied so until dm-more/rails_datamapper catches up, ut I hope these two project merge someday again.
36
+ email:
37
+ - m.kristian@web.de
38
+ executables: []
39
+
40
+ extensions: []
41
+
42
+ extra_rdoc_files:
43
+ - History.txt
44
+ - Manifest.txt
45
+ - README.txt
46
+ files:
47
+ - History.txt
48
+ - Manifest.txt
49
+ - README.txt
50
+ - Rakefile
51
+ - generators/dm_install/dm_install_generator.rb
52
+ - generators/dm_install/templates/datamapper.rake
53
+ - generators/dm_migration/dm_migration_generator.rb
54
+ - generators/dm_migration/templates/migration.rb
55
+ - generators/dm_model/dm_model_generator.rb
56
+ - generators/dm_model/templates/model.rb
57
+ - generators/dm_model/templates/unit_test.rb
58
+ - generators/rspec_default_values.rb
59
+ - generators/rspec_dm_model/rspec_dm_model_generator.rb
60
+ - generators/rspec_dm_model/templates/model.rb
61
+ - generators/rspec_dm_model/templates/model_spec.rb
62
+ - lib/datamapper4rails.rb
63
+ - lib/datamapper4rails/adapters/base_adapter.rb
64
+ - lib/datamapper4rails/adapters/restful_adapter.rb
65
+ - lib/datamapper4rails/database_config.rb
66
+ - lib/datamapper4rails/datamapper_store.rb
67
+ - lib/datamapper4rails/restful_transactions.rb
68
+ - lib/datamapper4rails/version.rb
69
+ has_rdoc: true
70
+ homepage: http://datamapper4rail.rubyforge.org
71
+ post_install_message:
72
+ rdoc_options:
73
+ - --main
74
+ - README.txt
75
+ require_paths:
76
+ - lib
77
+ required_ruby_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: "0"
82
+ version:
83
+ required_rubygems_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: "0"
88
+ version:
89
+ requirements: []
90
+
91
+ rubyforge_project: datamapper4rail
92
+ rubygems_version: 1.3.1
93
+ signing_key:
94
+ specification_version: 2
95
+ summary: collection of datamapper related extensions
96
+ test_files: []
97
+