diametric 0.0.1 → 0.0.2
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.
- 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
|