diametric 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +14 -3
- data/README.md +26 -1
- data/Rakefile +17 -1
- data/diametric.gemspec +8 -7
- data/lib/diametric.rb +8 -0
- data/lib/diametric/config.rb +54 -0
- data/lib/diametric/config/environment.rb +42 -0
- data/lib/diametric/entity.rb +126 -28
- data/lib/diametric/generators/active_model.rb +42 -0
- data/lib/diametric/persistence.rb +49 -0
- data/lib/diametric/persistence/common.rb +21 -0
- data/lib/diametric/persistence/peer.rb +17 -11
- data/lib/diametric/persistence/rest.rb +14 -6
- data/lib/diametric/query.rb +34 -3
- data/lib/diametric/railtie.rb +52 -0
- data/lib/diametric/version.rb +1 -1
- data/lib/tasks/create_schema.rb +27 -0
- data/lib/tasks/diametric_config.rb +45 -0
- data/spec/diametric/config_spec.rb +60 -0
- data/spec/diametric/entity_spec.rb +96 -27
- data/spec/diametric/persistence/peer_spec.rb +2 -2
- data/spec/diametric/persistence/rest_spec.rb +10 -3
- data/spec/diametric/persistence_spec.rb +59 -0
- data/spec/diametric/query_spec.rb +56 -0
- data/spec/integration_spec.rb +29 -1
- data/spec/peer_integration_spec.rb +37 -0
- data/spec/spec_helper.rb +23 -0
- data/spec/support/gen_entity_class.rb +7 -0
- data/spec/support/persistence_examples.rb +33 -1
- metadata +36 -13
- data/.gitignore +0 -22
- data/Guardfile +0 -8
- data/TODO.org +0 -12
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'rails/generators/active_model'
|
2
|
+
|
3
|
+
module Diametric
|
4
|
+
module Generators
|
5
|
+
|
6
|
+
class ActiveModel < ::Rails::Generators::ActiveModel #:nodoc:
|
7
|
+
def self.all(klass)
|
8
|
+
"#{klass}.all"
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.find(klass, params=nil)
|
12
|
+
"#{klass}.get(#{params})"
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.build(klass, params=nil)
|
16
|
+
if params
|
17
|
+
"#{klass}.new(#{params})"
|
18
|
+
else
|
19
|
+
"#{klass}.new"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def save
|
24
|
+
"#{name}.save"
|
25
|
+
end
|
26
|
+
|
27
|
+
def update_attributes(params=nil)
|
28
|
+
#"#{name}.update_attributes(#{params})"
|
29
|
+
"#{name}.update(#{params})"
|
30
|
+
end
|
31
|
+
|
32
|
+
def errors
|
33
|
+
"#{name}.errors"
|
34
|
+
end
|
35
|
+
|
36
|
+
def destroy
|
37
|
+
"#{name}.destroy"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Diametric
|
2
|
+
# Persistence is the main entry point for adding persistence to your diametric entities.
|
3
|
+
module Persistence
|
4
|
+
class << self; attr_reader :conn_type; end
|
5
|
+
autoload :REST, 'diametric/persistence/rest'
|
6
|
+
|
7
|
+
# Establish a base connection for your application that is used unless specified otherwise. This method can
|
8
|
+
# establish connections in either REST or peer mode, depending on the supplied options.
|
9
|
+
#
|
10
|
+
# @example Connecting in peer-mode (JRuby required)
|
11
|
+
# Diametric::Persistence.establish_base_connection({:uri => "datomic:free://localhost:4334/my-db"})
|
12
|
+
#
|
13
|
+
# @example Connecting in REST-mode
|
14
|
+
# Diametric::Persistence.establish_base_connection({:uri => "http://localhost:9000/",
|
15
|
+
# :database => "my-db",
|
16
|
+
# :storage => "my-dbalias"})
|
17
|
+
def self.establish_base_connection(options)
|
18
|
+
@_persistence_class = persistence_class(options[:uri])
|
19
|
+
@_persistence_class.connect(options)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Including +Diametric::Persistence+ after establishing a base connection will include
|
23
|
+
# the appropriate Persistence class ({REST} or {Peer})
|
24
|
+
def self.included(base)
|
25
|
+
base.send(:include, @_persistence_class) if @_persistence_class
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.peer?
|
29
|
+
@conn_type == :peer
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.rest?
|
33
|
+
@conn_type == :rest
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def self.persistence_class(uri)
|
39
|
+
if uri =~ /^datomic:/
|
40
|
+
require 'diametric/persistence/peer'
|
41
|
+
@conn_type = :peer
|
42
|
+
Peer
|
43
|
+
else
|
44
|
+
@conn_type = :rest
|
45
|
+
REST
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -3,6 +3,23 @@ module Diametric
|
|
3
3
|
module Common
|
4
4
|
def self.included(base)
|
5
5
|
base.send(:extend, ClassMethods)
|
6
|
+
base.send(:include, InstanceMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
module InstanceMethods
|
10
|
+
def update_attributes(new_attributes)
|
11
|
+
assign_attributes(new_attributes)
|
12
|
+
save
|
13
|
+
end
|
14
|
+
|
15
|
+
def assign_attributes(new_attributes)
|
16
|
+
valid_keys = attribute_names + [:id]
|
17
|
+
new_attributes.each do |key, value|
|
18
|
+
if valid_keys.include? key.to_sym
|
19
|
+
self.send("#{key}=", value)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
6
23
|
end
|
7
24
|
|
8
25
|
module ClassMethods
|
@@ -10,6 +27,10 @@ module Diametric
|
|
10
27
|
transact(schema)
|
11
28
|
end
|
12
29
|
|
30
|
+
def all
|
31
|
+
Diametric::Query.new(self).all
|
32
|
+
end
|
33
|
+
|
13
34
|
def first(conditions = {})
|
14
35
|
where(conditions).first
|
15
36
|
end
|
@@ -41,7 +41,8 @@ module Diametric
|
|
41
41
|
module ClassMethods
|
42
42
|
include_package "datomic"
|
43
43
|
|
44
|
-
def connect(
|
44
|
+
def connect(options = {})
|
45
|
+
uri = options[:uri]
|
45
46
|
Java::Datomic::Peer.create_database(uri)
|
46
47
|
@connection = Java::Datomic::Peer.connect(uri)
|
47
48
|
end
|
@@ -61,16 +62,12 @@ module Diametric
|
|
61
62
|
end
|
62
63
|
|
63
64
|
def get(dbid)
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
entity = self.new(Hash[*attrs.flatten])
|
72
|
-
entity.dbid = dbid
|
73
|
-
|
65
|
+
entity = self.new
|
66
|
+
self.all.each do |e|
|
67
|
+
next unless e.dbid == dbid.to_i
|
68
|
+
e.attribute_names.each {|n| entity.send((n.to_s+"=").to_sym, e.send(n))}
|
69
|
+
end
|
70
|
+
entity.dbid = dbid.to_i
|
74
71
|
entity
|
75
72
|
end
|
76
73
|
|
@@ -102,6 +99,15 @@ module Diametric
|
|
102
99
|
|
103
100
|
res
|
104
101
|
end
|
102
|
+
|
103
|
+
def to_edn
|
104
|
+
self.dbid
|
105
|
+
end
|
106
|
+
|
107
|
+
def retract_entity(dbid)
|
108
|
+
query = [Java::Datomic::Util.list(":db.fn/retractEntity", dbid)]
|
109
|
+
self.class.connection.transact(query).get
|
110
|
+
end
|
105
111
|
end
|
106
112
|
end
|
107
113
|
end
|
@@ -2,6 +2,7 @@ require 'diametric'
|
|
2
2
|
require 'diametric/persistence/common'
|
3
3
|
require 'datomic/client'
|
4
4
|
|
5
|
+
# TODO: nice errors when unable to connect
|
5
6
|
module Diametric
|
6
7
|
module Persistence
|
7
8
|
module REST
|
@@ -25,13 +26,13 @@ module Diametric
|
|
25
26
|
end
|
26
27
|
|
27
28
|
module ClassMethods
|
28
|
-
def connect(
|
29
|
-
@uri = uri
|
30
|
-
@dbalias =
|
31
|
-
@database = database
|
29
|
+
def connect(options = {})
|
30
|
+
@uri = options[:uri]
|
31
|
+
@dbalias = options[:storage]
|
32
|
+
@database = options[:database]
|
32
33
|
|
33
|
-
@connection = Datomic::Client.new(uri, dbalias)
|
34
|
-
@connection.create_database(database)
|
34
|
+
@connection = Datomic::Client.new(@uri, @dbalias)
|
35
|
+
@connection.create_database(@database)
|
35
36
|
end
|
36
37
|
|
37
38
|
def connection
|
@@ -73,6 +74,8 @@ module Diametric
|
|
73
74
|
return false unless valid?
|
74
75
|
return true unless changed?
|
75
76
|
|
77
|
+
self.dbid = self.dbid.to_i if self.dbid.class == String
|
78
|
+
|
76
79
|
res = self.class.transact(tx_data)
|
77
80
|
if dbid.nil?
|
78
81
|
self.dbid = res.data[:tempids].values.first
|
@@ -83,6 +86,11 @@ module Diametric
|
|
83
86
|
|
84
87
|
res
|
85
88
|
end
|
89
|
+
|
90
|
+
def retract_entity(dbid)
|
91
|
+
query = [[:"db.fn/retractEntity", dbid.to_i]]
|
92
|
+
self.class.transact(query)
|
93
|
+
end
|
86
94
|
end
|
87
95
|
end
|
88
96
|
end
|
data/lib/diametric/query.rb
CHANGED
@@ -91,7 +91,8 @@ module Diametric
|
|
91
91
|
# TODO check to see if the model has a `.q` method and give
|
92
92
|
# an appropriate error if not.
|
93
93
|
res = model.q(*data)
|
94
|
-
|
94
|
+
|
95
|
+
collapse_results(res).each do |entity|
|
95
96
|
# The map is for compatibility with Java peer persistence.
|
96
97
|
# TODO remove if possible
|
97
98
|
yield model.from_query(entity.map { |x| x })
|
@@ -112,11 +113,11 @@ module Diametric
|
|
112
113
|
# is the Datomic query composed of Ruby data. The second element is
|
113
114
|
# the arguments that used with the query.
|
114
115
|
def data
|
115
|
-
vars = model.
|
116
|
+
vars = model.attribute_names.map { |attribute| ~"?#{attribute}" }
|
116
117
|
|
117
118
|
from = conditions.map { |k, _| ~"?#{k}" }
|
118
119
|
|
119
|
-
clauses = model.
|
120
|
+
clauses = model.attribute_names.map { |attribute|
|
120
121
|
[~"?e", model.namespace(model.prefix, attribute), ~"?#{attribute}"]
|
121
122
|
}
|
122
123
|
clauses += filters
|
@@ -153,5 +154,35 @@ module Diametric
|
|
153
154
|
element
|
154
155
|
end
|
155
156
|
end
|
157
|
+
|
158
|
+
def collapse_results(res)
|
159
|
+
res.group_by(&:first).map do |dbid, results|
|
160
|
+
# extract dbid from results
|
161
|
+
# NOTE: we prefer to_a.drop(1) over block arg decomposition because
|
162
|
+
# result may be a Java::ClojureLang::PersistentVector
|
163
|
+
results = results.map {|result| result.to_a.drop(1) }
|
164
|
+
|
165
|
+
# Group values from all results into one result set
|
166
|
+
# [["b", 123], ["c", 123]] #=> [["b", "c"], [123, 123]]
|
167
|
+
grouped_values = results.transpose
|
168
|
+
|
169
|
+
# Attach attribute names to each collection of values
|
170
|
+
# => [[:letters, ["b", "c"]], [:number, [123, 123]]]
|
171
|
+
attr_to_values = model.attributes.keys.zip(grouped_values)
|
172
|
+
|
173
|
+
# Retain cardinality/many attributes as a collection,
|
174
|
+
# but pick only one value for cardinality/one attributes
|
175
|
+
collapsed_values = attr_to_values.map do |attr, values|
|
176
|
+
if model.attributes[attr][:cardinality] == :many
|
177
|
+
values
|
178
|
+
else
|
179
|
+
values.first
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# Returning a singular result for each dbid
|
184
|
+
[dbid, *collapsed_values]
|
185
|
+
end
|
186
|
+
end
|
156
187
|
end
|
157
188
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "diametric"
|
3
|
+
require "diametric/config"
|
4
|
+
|
5
|
+
require "rails"
|
6
|
+
|
7
|
+
require 'diametric/generators/active_model'
|
8
|
+
|
9
|
+
module Rails
|
10
|
+
module Diametric
|
11
|
+
class Railtie < Rails::Railtie
|
12
|
+
|
13
|
+
config.app_generators.orm :diametric
|
14
|
+
|
15
|
+
# Initialize Diametric. This will look for a diametric.yml in the config
|
16
|
+
# directory and configure diametric appropriately.
|
17
|
+
initializer "setup database" do
|
18
|
+
config_file = Rails.root.join("config", "diametric.yml")
|
19
|
+
if config_file.file?
|
20
|
+
begin
|
21
|
+
::Diametric::Config.load_and_connect!(config_file)
|
22
|
+
rescue Exception => e
|
23
|
+
handle_configuration_error(e)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# After initialization we will warn the user if we can't find a diametric.yml and
|
29
|
+
# alert to create one.
|
30
|
+
initializer "warn when configuration is missing" do
|
31
|
+
config.after_initialize do
|
32
|
+
unless Rails.root.join("config", "diametric.yml").file? || ::Diametric::Config.configured?
|
33
|
+
puts "\nDiametric config not found. Create a config file at: config/diametric.yml"
|
34
|
+
puts "to generate one run: rails generate diametric:config\n\n"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
rake_tasks do
|
39
|
+
require "#{File.join(File.dirname(__FILE__), "..", "tasks", "create_schema.rb")}"
|
40
|
+
require "#{File.join(File.dirname(__FILE__), "..", "tasks", "diametric_config.rb")}"
|
41
|
+
end
|
42
|
+
# Rails runs all initializers first before getting into any generator
|
43
|
+
# code, so we have no way in the initializer to know if we are
|
44
|
+
# generating a diametric.yml. So instead of failing, we catch all the
|
45
|
+
# errors and print them out.
|
46
|
+
def handle_configuration_error(e)
|
47
|
+
puts "There is a configuration error with the current diametric.yml."
|
48
|
+
puts e.message
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/diametric/version.rb
CHANGED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'rake'
|
2
|
+
namespace :diametric do
|
3
|
+
desc "Create schema on datomic for REST connection"
|
4
|
+
task :create_schema => :environment do
|
5
|
+
create_schema
|
6
|
+
end
|
7
|
+
|
8
|
+
# intentionally, no desc for this task
|
9
|
+
# this task is used inside of the rails app
|
10
|
+
task :create_schema_for_peer do
|
11
|
+
create_schema(false)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def create_schema(print_info=true)
|
16
|
+
puts "Creating all schemas ..." if print_info
|
17
|
+
Dir.glob(File.join(Rails.root, "app", "models", "*.rb")).each {|model| load model}
|
18
|
+
Module.constants.each do |const|
|
19
|
+
class_def = eval "#{const.to_s}"
|
20
|
+
if class_def.respond_to? :create_schema
|
21
|
+
class_def.send(:create_schema)
|
22
|
+
puts class_def.send(:schema) if print_info
|
23
|
+
end
|
24
|
+
end
|
25
|
+
puts "done" if print_info
|
26
|
+
end
|
27
|
+
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# This rake task creates config/diametric.yml file.
|
2
|
+
# usage:
|
3
|
+
# rake diametric:config[REST,dbname,port,storage] ;=> for REST connection
|
4
|
+
# rake diametric:config[PEER,dbname] ;=> for PEER connection
|
5
|
+
# rake diametric:config[FREE,dbname,port] ;=> for Free transactor connection
|
6
|
+
# examples:
|
7
|
+
# rake diametric:config[REST,sample,9000,test] ;=> for REST, bin/rest 9000 test datomic:mem://
|
8
|
+
# rake diametric:config[PEER,sample] ;=> for PEER
|
9
|
+
# rake diametric:config[FREE,sample,4334] ;=> for Free transactor
|
10
|
+
#
|
11
|
+
# note:
|
12
|
+
# For supporting pro version, perhaps, this should cover more configurations.
|
13
|
+
# refs: http://docs.datomic.com/clojure/index.html#datomic.api/connect
|
14
|
+
|
15
|
+
require 'rake'
|
16
|
+
namespace :diametric do
|
17
|
+
desc "Generate config/diametric.yml"
|
18
|
+
task :config, [:type, :database, :port, :storage] => [:environment] do |t, args|
|
19
|
+
config = {"development" => {}}
|
20
|
+
case args[:type]
|
21
|
+
when "REST"
|
22
|
+
config["development"]["uri"]=rest_uri(args)
|
23
|
+
config["development"]["storage"]=args[:storage]
|
24
|
+
config["development"]["database"]=args[:database]
|
25
|
+
when "PEER"
|
26
|
+
config["development"]["uri"]=peer_uri(args)
|
27
|
+
when "FREE"
|
28
|
+
config["development"]["uri"]=free_uri(args)
|
29
|
+
end
|
30
|
+
puts config.to_yaml
|
31
|
+
File.open(File.join(Rails.root, "config", "diametric.yml"), "w").write(config.to_yaml)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def rest_uri(args)
|
36
|
+
["http://localhost:", args[:port]].join
|
37
|
+
end
|
38
|
+
|
39
|
+
def peer_uri(args)
|
40
|
+
["datomic:mem://", args[:database]].join
|
41
|
+
end
|
42
|
+
|
43
|
+
def free_uri(args)
|
44
|
+
["datomic:free://localhost:", args[:port], "/", args[:database]].join
|
45
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Diametric::Config do
|
4
|
+
before do
|
5
|
+
Diametric::Config.configuration.clear
|
6
|
+
end
|
7
|
+
|
8
|
+
describe ".configuration" do
|
9
|
+
it "is empty by default" do
|
10
|
+
Diametric::Config.configuration.should have(0).options
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe ".configured?" do
|
15
|
+
it "is true if configuration is present" do
|
16
|
+
Diametric::Config.configuration[:uri] = 'datomic:free://sample'
|
17
|
+
Diametric::Config.should be_configured
|
18
|
+
end
|
19
|
+
|
20
|
+
it "is false if no configuration has been added" do
|
21
|
+
Diametric::Config.should_not be_configured
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe ".load_and_connect!" do
|
26
|
+
let(:path) { "/path/to/diametric.yml" }
|
27
|
+
let(:env) { :test }
|
28
|
+
let(:settings) { {'uri' => 'diametric:free://test'} }
|
29
|
+
|
30
|
+
it "loads settings from the environment" do
|
31
|
+
Diametric::Config::Environment.should_receive(:load_yaml).with(path, env).and_return(settings)
|
32
|
+
Diametric::Config.stub(:connect!)
|
33
|
+
|
34
|
+
Diametric::Config.load_and_connect!(path, env)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "sets the configuration" do
|
38
|
+
Diametric::Config::Environment.stub(:load_yaml => settings)
|
39
|
+
Diametric::Config.stub(:connect!)
|
40
|
+
Diametric::Config.load_and_connect!(path, env)
|
41
|
+
|
42
|
+
Diametric::Config.configuration.should == settings
|
43
|
+
end
|
44
|
+
|
45
|
+
it "connects" do
|
46
|
+
Diametric::Config::Environment.stub(:load_yaml => settings)
|
47
|
+
Diametric::Config.should_receive(:connect!).with(settings)
|
48
|
+
|
49
|
+
Diametric::Config.load_and_connect!(path, env)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe ".connect!" do
|
54
|
+
it "establishes a base connection" do
|
55
|
+
settings = stub
|
56
|
+
Diametric::Persistence.should_receive(:establish_base_connection).with(settings)
|
57
|
+
Diametric::Config.connect!(settings)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|