diametric 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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