dm-mnesia 0.2.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2 @@
1
+ *.gem
2
+ *.swp
@@ -0,0 +1,7 @@
1
+ Manifest
2
+ README.rdoc
3
+ Rakefile
4
+ lib/dm-mnesia-adapter.rb
5
+ spec/dm-mnesia-adapter_spec.rb
6
+ spec/spec.opts
7
+ spec/spec_helper.rb
@@ -0,0 +1,46 @@
1
+ = dm-mnesia
2
+
3
+ A DataMapper adapter for Erlang's Mnesia db
4
+
5
+ ==Requirements
6
+
7
+ You will need to install Rbridge[http://github.com/grockit/rulang].
8
+
9
+ ==Installation
10
+
11
+ sudo gem install dm-mnesia
12
+
13
+ ==Usage
14
+
15
+ Require the gem in your enviroment file
16
+ config.gem "dm-mnesia"
17
+ Initialize a connection using the adapter
18
+ connection = { :adapter => 'Mnesia',
19
+ :hostname => 'localhost',
20
+ :port => 9900,
21
+ :ids_table => 'NAME OF THE TABLE TO STORE UNIQUE IDS.',
22
+ :id_function=> 'NAME OF AN ERLANG FUNCTION TO CREATE UNIQUE IDS FOR YOUR RECORDS'}
23
+ DataMapper.setup(:default, connection )
24
+
25
+ We used this[http://dudefrommangalore.blogspot.com/2009/03/auto-increment-in-mnesia-database.html] example for the ids_table option </p>
26
+
27
+ Now you can define your models just like you would if you were using the DataMapper default adapters. All CRUD operations work the same as well.
28
+
29
+ ==Issues
30
+
31
+ When using a BinaryString type as a key for your model, you must set it to ":require=>false", otherwise it won't work
32
+ When using a List type, you must assign the value to the column like so:
33
+ Model.mycolumn = ["value", "value"]
34
+
35
+
36
+ ==To Do List
37
+
38
+ In certain cases, the read command retrieves all record from Mnesia and filters, sorts them in ruby, more work needs to be done make this more efficient. This won't be a problem if you are working with records around 2000.
39
+ Add missing Erlang types: Tuple, Atom.
40
+
41
+ Authors: Chad DePue (mailto:chad@inakanetworks.com), Manuel Gómez (manuel@inakanetworks.com)
42
+
43
+
44
+
45
+
46
+
@@ -0,0 +1,21 @@
1
+ require 'rake'
2
+ begin
3
+ require 'jeweler'
4
+ Jeweler::Tasks.new do |gemspec|
5
+ gemspec.name = "dm-mnesia"
6
+ gemspec.summary = "DataMapper adapter for Erlang's mnesia db"
7
+ gemspec.description = ""
8
+ gemspec.email = "chad@inakanetworks.com,manuel@inakanetworks.com"
9
+ gemspec.homepage = "http://github.com/mergoc/dm-mnesia"
10
+ gemspec.authors = ["Chad DePue","Manuel Gomez"]
11
+ end
12
+ Jeweler::GemcutterTasks.new
13
+ rescue LoadError
14
+ puts "Jeweler not available. Install it with: gem install jeweler"
15
+ end
16
+
17
+ require 'spec/rake/spectask'
18
+ Spec::Rake::SpecTask.new(:spec) do |spec|
19
+ spec.libs << 'lib' << 'spec'
20
+ spec.spec_files = FileList['spec/**/*_spec.rb']
21
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.8
@@ -0,0 +1,55 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{dm-mnesia}
8
+ s.version = "0.2.8"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Chad DePue", "Manuel Gomez"]
12
+ s.date = %q{2010-08-30}
13
+ s.description = %q{}
14
+ s.email = %q{chad@inakanetworks.com,manuel@inakanetworks.com}
15
+ s.extra_rdoc_files = [
16
+ "README.rdoc"
17
+ ]
18
+ s.files = [
19
+ ".gitignore",
20
+ "Manifest",
21
+ "README.rdoc",
22
+ "Rakefile",
23
+ "VERSION",
24
+ "dm-mnesia.gemspec",
25
+ "lib/dm-mnesia-adapter.rb",
26
+ "lib/dm-mnesia.rb",
27
+ "lib/types/binary_string.rb",
28
+ "lib/types/list.rb",
29
+ "lib/types/record.rb",
30
+ "lib/types/tuple.rb",
31
+ "pkg/dm-mnesia-0.0.0.gem",
32
+ "spec/dm-mnesia-adapter_spec.rb",
33
+ "spec/spec.opts",
34
+ "spec/spec_helper.rb"
35
+ ]
36
+ s.homepage = %q{http://github.com/mergoc/dm-mnesia}
37
+ s.rdoc_options = ["--charset=UTF-8"]
38
+ s.require_paths = ["lib"]
39
+ s.rubygems_version = %q{1.3.7}
40
+ s.summary = %q{DataMapper adapter for Erlang's mnesia db}
41
+ s.test_files = [
42
+ "spec/dm-mnesia-adapter_spec.rb",
43
+ "spec/spec_helper.rb"
44
+ ]
45
+
46
+ if s.respond_to? :specification_version then
47
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
48
+ s.specification_version = 3
49
+
50
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
51
+ else
52
+ end
53
+ else
54
+ end
55
+ end
@@ -0,0 +1,265 @@
1
+ require 'dm-core'
2
+ require 'dm-core/adapters/abstract_adapter'
3
+ require 'rbridge'
4
+ module DataMapper::Adapters
5
+
6
+ class MnesiaAdapter < AbstractAdapter
7
+
8
+
9
+ #Initialize the adapter with the given options
10
+ # 1. hostname: location of the rbridge server as an Ip or url, default is localhost
11
+ # 2. port: the port to comunicate with rbridge, default is 9900
12
+ # 3. mod: rbridge offers several modes of operation.
13
+ # 4. ids_table: the table in mnesia that will handle the ids of each record created
14
+ # 5. id_function: in case an erlang function is wished to be used for record id generations, it can be set here. If the id is a BinaryString, this is required
15
+ def initialize(name, options)
16
+ super
17
+
18
+ @hosname = @options[:hostname] ||= "localhost"
19
+ @port = @options[:port] ||= 9900
20
+ @mod = @options[:mod]
21
+ @ids_table = @options[:ids_table]
22
+ @id_function = @options[:id_function]
23
+ @con = con
24
+ @table_attributes = nil
25
+
26
+ ## Refreshes the spec table so that spec test run properly
27
+ con.erl("mnesia:clear_table(heffalump).") if name == :default
28
+ end
29
+
30
+ # Initializes the connection to the rbridge server
31
+ def con(&blk)
32
+ # connect to the server
33
+ if @con.nil?
34
+ @con = RBridge.new(@mod,@hostname, @port)
35
+ if @con.erl("1*1.") != 1
36
+ raise "Error opening connection to database: #{@con.erl("1*1.")}"
37
+ end
38
+ end
39
+ @con
40
+ end
41
+
42
+ # Create function required by the adapter spec
43
+ def create(resources)
44
+ resources.each do |resource|
45
+ initialize_identity_field(resource,get_key(resource))
46
+ save(con, parse_resource(resource))
47
+ end
48
+ end
49
+
50
+ # Read function required by the adapter spec
51
+ #TODO change query so that the mnesia filters the results
52
+ # and does not bring all records on the table every time
53
+ def read(query)
54
+ temp_records = erl_read(query)
55
+ records = []
56
+ temp_records.each do |value|
57
+ records << value if value
58
+ end
59
+ result = query.filter_records(records.reverse)
60
+ result
61
+ end
62
+
63
+ # Update function required by the adapter spec
64
+ def update(attributes, collection)
65
+ attributes = attributes_as_fields(attributes)
66
+ collection.each do |resource|
67
+ attributes = resource.attributes(:field).merge(attributes)
68
+ save(con, parse_resource(resource))
69
+ end
70
+ end
71
+
72
+ # Delete function required by the adapter spec
73
+ def delete(collection)
74
+ collection.each do |resource|
75
+ erl_delete(resource.model,resource.key.first)
76
+ end
77
+ end
78
+
79
+
80
+ protected
81
+
82
+ # On record creation the primary id must be generated
83
+ # If the id_function option is set and the key for the model is a binary string, an erlan function is used.
84
+ # otherwise, and mnesia unique id table can be used or a random number
85
+ def get_key resource
86
+ model = resource.model
87
+ identity_field = get_identity_field resource
88
+ if !@id_function.nil? and identity_field.is_a? DataMapper::Property::BinaryString
89
+ con.erl("#{@id_function}.")
90
+ elsif @ids_tables.nil?
91
+ con.erl("mnesia:dirty_update_counter(#{@ids_table},#{model.to_s.downcase},1).")
92
+ else
93
+ rand(2**32)
94
+ end
95
+ end
96
+
97
+
98
+ # Before update or create the resource must be parse to be properly pass to rbridge
99
+ def parse_resource(resource)
100
+ model = resource.model
101
+ fields = []
102
+ types = Hash.new
103
+ # Get all properties defined on the datamapper model
104
+ resource.send(:properties).map do |property|
105
+ if property.is_a? DataMapper::Property::Serial
106
+ types[property.name] = 0
107
+ elsif property.is_a? DataMapper::Property::Integer
108
+ types[property.name] = 0
109
+ elsif property.is_a? DataMapper::Property::Boolean
110
+ types[property.name] = false
111
+ elsif property.is_a? DataMapper::Property::Record
112
+ types[property.name] = {}
113
+ elsif property.is_a? DataMapper::Property::List
114
+ types[property.name] = []
115
+ elsif property.is_a? DataMapper::Property::BinaryString
116
+ types[property.name] = 0b100
117
+ elsif property.is_a? DataMapper::Property::DateTime
118
+ types[property.name] = Time.now
119
+ elsif property.is_a? DataMapper::Property::String
120
+ types[property.name] = ""
121
+ end
122
+
123
+
124
+ end
125
+
126
+ # Set the value for the identity field
127
+ identity_field = get_identity_field resource
128
+ fields.push(identity_field.dump(resource.key.first))
129
+
130
+ # Loop through the attributes defined in the mnesia record
131
+ # and set the value for each one except the identity field
132
+ table_attributes(model).each do |p|
133
+ if p.to_s!= identity_field.field
134
+ if types[p.to_sym].is_a? Integer
135
+ value = resource[p.to_sym] == nil ? 'undefined' : resource[p.to_sym]
136
+ fields.push(value)
137
+ elsif types[p.to_sym].is_a? Array
138
+ field = resource.model.properties.detect{|f|f.field == p.to_s}
139
+ value = resource[p.to_sym]
140
+ if resource[p.to_sym].length == 0
141
+ fields.push("'undefined'")
142
+ else
143
+ fields.push(field.dump(value))
144
+ end
145
+ elsif types[p.to_sym].is_a? Hash
146
+ value = resource[p.to_sym]
147
+ if resource[p.to_sym].nil?
148
+ fields.push("'undefined'")
149
+ else
150
+ fields.push(value)
151
+ end
152
+ else
153
+ if resource[p.to_sym].nil?
154
+ fields.push("'undefined'")
155
+ else
156
+ fields.push("\"" + resource[p.to_sym].to_s + "\"")
157
+ end
158
+ end
159
+
160
+ end
161
+ end
162
+ "#{model.to_s.downcase},{#{model.to_s.downcase},#{fields.join(',').to_s}}"
163
+ end
164
+
165
+
166
+ # Function called by create and update
167
+ def save(con, record)
168
+ if !erl_save(con,record)
169
+ raise WriteError, erl_save(con,record)
170
+ end
171
+ end
172
+
173
+ # Retrived the attribues defined for the table in mnesia
174
+ def table_attributes model
175
+ if @table_attribute.nil?
176
+ @table_attributes = con.erl("mnesia:table_info(" + model.to_s.downcase + ",attributes).")
177
+ end
178
+ @table_attributes
179
+ end
180
+
181
+ # Set the initial value for the identity field on record creation
182
+ def initialize_identity_field resource, value
183
+ identity_field = get_identity_field resource
184
+ identity_field.set(resource,value)
185
+ end
186
+
187
+ # Returns the field object defined as a key in the model
188
+ def get_identity_field resource
189
+ resource.model.key(name).detect{|p|p.key?}
190
+ end
191
+
192
+ # Gets the id value from the conditions passed in the query
193
+ def get_id_from_conditions conditions
194
+ conditions.map do |condition|
195
+ return condition.loaded_value if condition.subject.name == :id
196
+ end
197
+ nil
198
+ end
199
+
200
+ # ERLAND FUNCTIONS
201
+ def erl_save(con,record)
202
+ con.erl("mnesia:dirty_write(#{record}).")
203
+ end
204
+
205
+ # Reads the recuested records from mnesia and parses them to be pased to the Read function
206
+ def erl_read query
207
+ model = query.model
208
+ if query.limit.nil?
209
+ records = con.erl("mnesia:transaction(fun() ->qlc:eval( qlc:q([ X || X <- mnesia:table(#{model.to_s.downcase}) ]))end ).")[1]
210
+ elsif query.limit == 1 && !get_id_from_conditions(query.conditions).nil?
211
+ id = get_id_from_conditions(query.conditions)
212
+ if id.is_a? Integer
213
+ records = con.erl("mnesia:dirty_read({#{model.to_s.downcase},#{id}}).")
214
+ else
215
+ records = con.erl("mnesia:dirty_read({#{model.to_s.downcase},<<\"" + id.to_s + "\">>}).")
216
+ end
217
+ elsif query.limit > 1
218
+ records = con.erl("mnesia:transaction(fun() ->qlc:eval( qlc:next_answers(qlc:cursor(qlc:q([ X || X <- mnesia:table(#{query.model.to_s.downcase}) ])),#{query.limit}) )end ).")[1]
219
+ else
220
+ records = con.erl("mnesia:transaction(fun() ->qlc:eval( qlc:q([ X || X <- mnesia:table(#{model.to_s.downcase}) ]))end ).")[1]
221
+ end
222
+
223
+ result = []
224
+ attributes = table_attributes model
225
+ records.each do | r |
226
+ r.delete(model.to_s.downcase)
227
+ h = Hash.new
228
+ r.each_with_index do |item,i|
229
+ h[attributes[i]] = r[i] == 'undefined' ? nil : r[i]
230
+ end
231
+ result << h
232
+ end
233
+ result.reverse
234
+ end
235
+
236
+ # Sends the delete command to erland.
237
+ def erl_delete model, id
238
+ command = ""
239
+ if id.is_a? Integer
240
+ command = "mnesia:dirty_delete({#{model.to_s.downcase},#{id.to_s}})."
241
+ else
242
+ command = "mnesia:dirty_delete({#{model.to_s.downcase},<<\"" + id.to_s + "\">>})."
243
+ end
244
+ if !con.erl(command)
245
+ raise WriteError, con.erl(command)
246
+ end
247
+
248
+ end
249
+
250
+
251
+
252
+
253
+ class ConnectError < StandardError
254
+ end
255
+
256
+ class WriteError < StandardError
257
+ end
258
+
259
+ class ReadError < StandardError
260
+ end
261
+
262
+
263
+ end
264
+
265
+ end
@@ -0,0 +1,8 @@
1
+ require 'dm-mnesia-adapter'
2
+ module DataMapper
3
+ class Property
4
+ autoload :Record, 'types/record'
5
+ autoload :BinaryString, 'types/binary_string'
6
+ autoload :List, 'types/list'
7
+ end
8
+ end
@@ -0,0 +1,37 @@
1
+ module DataMapper
2
+ class Property
3
+ class BinaryString < Text
4
+ def custom?
5
+ true
6
+ end
7
+
8
+ def primitive?(value)
9
+ value.kind_of?(::String)
10
+ end
11
+
12
+ def valid?(value, negated = false)
13
+ super || dump(value).kind_of?(::String)
14
+ end
15
+
16
+ def load(value)
17
+ if value.nil?
18
+ nil
19
+ elsif value.is_a?(::String)
20
+ value
21
+ else
22
+ raise ArgumentError.new("not sure what type #{value.class}")
23
+ end
24
+ end
25
+
26
+
27
+ def dump(value)
28
+ "<<\"#{value.to_s}\">>"
29
+ end
30
+
31
+ def typecast_to_primitive(value)
32
+ value.to_s
33
+ end
34
+ end
35
+ end
36
+ end
37
+
@@ -0,0 +1,43 @@
1
+ module DataMapper
2
+ class Property
3
+ class List < Text
4
+ def custom?
5
+ true
6
+ end
7
+
8
+ def primitive?(value)
9
+ value.kind_of?(::Array) || value.kind_of?(::String)
10
+ end
11
+
12
+ def valid?(value, negated = false)
13
+ super || dump(value).kind_of?(::String)
14
+ end
15
+
16
+ def load(value)
17
+ if value.nil?
18
+ Array.new
19
+ elsif value.is_a?(::Array)
20
+ value
21
+ elsif value.is_a?(::String)
22
+ value
23
+ else
24
+ raise ArgumentError.new("not sure what type #{value.class}")
25
+ end
26
+ end
27
+
28
+
29
+ def dump(value)
30
+ if value.is_a?(::String)
31
+ value
32
+ else
33
+ "[#{value.join(",").to_s}]"
34
+ end
35
+ end
36
+
37
+ def typecast_to_primitive(value)
38
+ value.to_s
39
+ end
40
+ end
41
+ end
42
+ end
43
+
@@ -0,0 +1,42 @@
1
+ module DataMapper
2
+ class Property
3
+ class Record < Text
4
+ def custom?
5
+ true
6
+ end
7
+
8
+ def primitive?(value)
9
+ value.kind_of?(::Array) || value.kind_of?(::Hash)
10
+ end
11
+
12
+ def valid?(value, negated = false)
13
+ super || dump(value).kind_of?(::String)
14
+ end
15
+
16
+ def load(value)
17
+ if value.nil?
18
+ nil
19
+ elsif value.is_a?(::Array)
20
+ klass = value.shift.capitalize
21
+ theklass = Kernel.const_get(klass).new
22
+ theklass.send('properties').map(&:name).each_with_index() do |k,i|
23
+ theklass.send("#{k}=",value[i])
24
+ end
25
+ theklass
26
+ else
27
+ raise ArgumentError.new("not sure what type #{value.class}")
28
+ end
29
+ end
30
+
31
+
32
+ def dump(value)
33
+ value
34
+ end
35
+
36
+ def typecast_to_primitive(value)
37
+ value.to_s
38
+ end
39
+ end
40
+ end
41
+ end
42
+
@@ -0,0 +1,12 @@
1
+ require 'dm-core'
2
+
3
+ module DataMapper
4
+ class Property
5
+ class BinaryString < Text
6
+ def custom?
7
+ true
8
+ end
9
+ end
10
+ end
11
+ end
12
+
@@ -0,0 +1,15 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+ require 'dm-core/spec/shared/adapter_spec'
3
+
4
+ describe DataMapper::Adapters::MnesiaAdapter do
5
+ before :all do
6
+ @adapter = DataMapper.setup(:default, :adapter => 'mnesia',
7
+ :hostname => 'localhost',
8
+ :port => 9900,
9
+ :mod => nil,
10
+ :ids_table => 'unique_ids')
11
+ end
12
+
13
+ it_should_behave_like 'An Adapter'
14
+
15
+ end
@@ -0,0 +1,4 @@
1
+ --colour
2
+ --loadby random
3
+ --format progress
4
+ --backtrace
@@ -0,0 +1,6 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+
4
+ require 'dm-mnesia'
5
+
6
+ require 'dm-core'
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dm-mnesia
3
+ version: !ruby/object:Gem::Version
4
+ hash: 7
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 2
9
+ - 8
10
+ version: 0.2.8
11
+ platform: ruby
12
+ authors:
13
+ - Chad DePue
14
+ - Manuel Gomez
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2010-08-30 00:00:00 -03:00
20
+ default_executable:
21
+ dependencies: []
22
+
23
+ description: ""
24
+ email: chad@inakanetworks.com,manuel@inakanetworks.com
25
+ executables: []
26
+
27
+ extensions: []
28
+
29
+ extra_rdoc_files:
30
+ - README.rdoc
31
+ files:
32
+ - .gitignore
33
+ - Manifest
34
+ - README.rdoc
35
+ - Rakefile
36
+ - VERSION
37
+ - dm-mnesia.gemspec
38
+ - lib/dm-mnesia-adapter.rb
39
+ - lib/dm-mnesia.rb
40
+ - lib/types/binary_string.rb
41
+ - lib/types/list.rb
42
+ - lib/types/record.rb
43
+ - lib/types/tuple.rb
44
+ - pkg/dm-mnesia-0.0.0.gem
45
+ - spec/dm-mnesia-adapter_spec.rb
46
+ - spec/spec.opts
47
+ - spec/spec_helper.rb
48
+ has_rdoc: true
49
+ homepage: http://github.com/mergoc/dm-mnesia
50
+ licenses: []
51
+
52
+ post_install_message:
53
+ rdoc_options:
54
+ - --charset=UTF-8
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ hash: 3
63
+ segments:
64
+ - 0
65
+ version: "0"
66
+ required_rubygems_version: !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ hash: 3
72
+ segments:
73
+ - 0
74
+ version: "0"
75
+ requirements: []
76
+
77
+ rubyforge_project:
78
+ rubygems_version: 1.3.7
79
+ signing_key:
80
+ specification_version: 3
81
+ summary: DataMapper adapter for Erlang's mnesia db
82
+ test_files:
83
+ - spec/dm-mnesia-adapter_spec.rb
84
+ - spec/spec_helper.rb