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.
@@ -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(uri)
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
- entity_map = connection.db.entity(dbid)
65
- attrs = entity_map.key_set.map { |attr_keyword|
66
- attr = attr_keyword.to_s.gsub(%r"^:\w+/", '')
67
- value = entity_map.get(attr_keyword)
68
- [attr, value]
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(uri, dbalias, database)
29
- @uri = uri
30
- @dbalias = 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
@@ -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
- res.each do |entity|
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.attributes.map { |attribute, _, _| ~"?#{attribute}" }
116
+ vars = model.attribute_names.map { |attribute| ~"?#{attribute}" }
116
117
 
117
118
  from = conditions.map { |k, _| ~"?#{k}" }
118
119
 
119
- clauses = model.attributes.map { |attribute, _, _|
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
@@ -1,3 +1,3 @@
1
1
  module Diametric
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -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