active-orient 0.3 → 0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: babcbd90fcc89be93302da2907357f3049ec4ae4
4
- data.tar.gz: a5a95791f21342e4272f58a777febe243f25af3f
3
+ metadata.gz: d00a72ebc77faf7333a31a4b5c73f17554ccfc88
4
+ data.tar.gz: 62cd8f3823a629dba2f10e8b91b7841c9359c29d
5
5
  SHA512:
6
- metadata.gz: c38d7aa2efb71e1d240f613fb9b1dd72475f846601f18a8b3d7fc1bfd9c49da289a8d4e64bf9f0cd765bf8a693ef2ed8fd079ea84bb806b830d92d4ee1b27301
7
- data.tar.gz: d9a94362684f5f988cc6f667c91e9094b77bf487d895dbbeab51321160d6377bc7515db4ec3bedfb3ed41846739313b3c27d9cff58a366b369b07390a88cccb5
6
+ metadata.gz: bdae72102cee0e3c39c07c74715e9167878e9be90f27d12da827dbae7419c0105adf77bb9cc43ecc6bcd5ae80c1f394bff8795865d3014d70758bc9f562a4d8b
7
+ data.tar.gz: 54415683a7bda1bfac78d73cf668fc93be875f654de0257e67f9c2a85963ef1315e156c2517b795713a4067635b6269710294a1cc2d74f09069de011a751b5ef
data/Gemfile CHANGED
@@ -3,6 +3,7 @@ gemspec
3
3
  gem 'activesupport' , "~>4.2"
4
4
  gem 'activemodel'
5
5
  gem 'rest-client', :git => 'git://github.com/rest-client/rest-client.git'
6
+ gem 'nokogiri', '~> 1.6.6' #, :git => 'git://github.com/sparklemotion/nokogiri.git'
6
7
  group :development, :test do
7
8
  gem "rspec"
8
9
  gem 'rspec-its'
data/README.md CHANGED
@@ -197,9 +197,57 @@ The Attributes "in" and "out" can be used to move across the graph
197
197
  start.e1[0].in.something
198
198
  ---> "nice
199
199
  ```
200
+ #### Queries
201
+ Contrary to traditional SQL-based Databases OrientDB handles subqueries very efficient.
202
+ In addition, OrientDB supports precompiled statements (let-Blocks).
203
+
204
+ ActiveOrient is equipped with a simple QueryGenerator: ActiveSupport::OrientQuery.
205
+ It works in two modi: a comprehensive and a subsequent one
206
+ ```ruby
207
+
208
+ q = OrientSupport::OrientQuery.new
209
+ q.from = Vertex
210
+ q.where << a: 2
211
+ q.where << 'b > 3 '
212
+ q.distinct = :profession
213
+ q.order = { :name => :asc }
214
+
215
+ ```
216
+ is equivalent to
217
+ ```ruby
218
+ q = OrientSupport::OrientQuery.new from: Vertex ,
219
+ where: [{ a: 2 }, 'b > 3 '],
220
+ distinct: :profession,
221
+ order: { :name => :asc }
222
+ q.to_s
223
+ => select distinct( profession ) from Vertex where a = 2 and b > 3 order by name asc
224
+ ```
225
+ Both modes can be mixed.
226
+
227
+ If subqueries are nessesary, they can be introduced as OrientSupport::OrientQuery or as »let-block«.
228
+ ```ruby
229
+ q = OrientSupport::OrientQuery.new from: 'ModelQuery'
230
+ q.let << "$city = adress.city"
231
+ q.where = "$city.country.name = 'Italy' OR $city.country.name = 'France'"
232
+ q.to_s
233
+ => select from ModelQuery let $city = adress.city where $city.country.name = 'Italy' OR $city.country.name = 'France'
234
+ ```
235
+ or
236
+ ```ruby
237
+ q = OrientSupport::OrientQuery.new
238
+ q.let << { a: OrientSupport::OrientQuery.new( from: '#5:0' ) }
239
+ q.let << { b: OrientSupport::OrientQuery.new( from: '#5:1' ) }
240
+ q.let << '$c= UNIONALL($a,$b) '
241
+ q.projection << 'expand( $c )'
242
+ q.to_s
243
+ => select expand( $c ) let $a = ( select from #5:0 ), $b = ( select from #5:1 ), $c= UNIONALL($a,$b)
244
+ ```
245
+
246
+
247
+
200
248
 
201
249
  #### Execute SQL-Commands
202
- At least - sql-commands can be executed as batch
250
+ Sql-commands can be executed as batch
203
251
 
204
252
  The ActiveOrient::Query-Class provides a Query-Stack and an Records-Array which keeps the results.
205
253
  The ActiveOrient::Query-Class acts as Parent-Class for aggregated Records (without a @rid), which are ActiveOrient::Model::Myquery Objects. If a Query returns a database-record, the correct ActiveOrient::Model-Class is instantiated.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3
1
+ 0.4
@@ -22,6 +22,6 @@ Gem::Specification.new do |s|
22
22
  s.add_development_dependency "bundler", "~> 1.8"
23
23
  s.add_development_dependency "rake", "~> 10.0"
24
24
  s.add_dependency 'activesupport', "~> 4.2"
25
- s.add_dependency 'rest-client', "~> 2.0.0.alpha"
25
+ s.add_dependency 'rest-client', "~> 2.0"
26
26
 
27
27
  end
@@ -0,0 +1,78 @@
1
+ =begin
2
+ Books Example
3
+
4
+ There are several Books. And there is a Table with Keywords. These are our Vertex-Classes
5
+
6
+ The Keywords are associated to the books, this is realized via an Edge »has_content«
7
+
8
+ This Example demonstrates how to build a query by using OrientSupport::OrientQuery
9
+ =end
10
+ class BooksExample
11
+
12
+ def initialize db, rebuild: true
13
+ if rebuild
14
+ db.delete_class :book
15
+ db.delete_class :keyword
16
+ db.delete_class :has_content
17
+ db.create_vertex_class :book
18
+ db.create_vertex_class :keyword
19
+ db.create_edge_class :has_content
20
+ ActiveOrient::Model::Keyword.create_property( :item , type: :string, index: :unique )
21
+ ActiveOrient::Model::Book.create_property( :title, type: :string, index: :unique )
22
+ end
23
+ end
24
+
25
+
26
+ def read_samples
27
+ fill_database = ->(sentence, this_book ) do
28
+ sentence.split(' ').each do |x|
29
+ this_word = Keyword.update_or_create where: { item: x }
30
+ this_edge = HC.create_edge from: this_book, to: this_word if this_word.present?
31
+ end
32
+ end
33
+ words = 'Die Geschäfte in der Industrie im wichtigen US-Bundesstaat New York sind im August so schlecht gelaufen wie seit mehr als sechs Jahren nicht mehr Der entsprechende Empire-State-Index fiel überraschend von plus Punkten im Juli auf minus 14,92 Zähler Dies teilte die New Yorker Notenbank Fed heut mit Bei Werten im positiven Bereich signalisiert das Barometer ein Wachstum Ökonomen hatten eigentlich mit einem Anstieg auf 5,0 Punkte gerechnet'
34
+ this_book = Book.create title: 'first'
35
+ fill_database[ words, this_book ]
36
+
37
+ words2 = 'Das Bruttoinlandsprodukt BIP in Japan ist im zweiten Quartal mit einer aufs Jahr hochgerechneten Rate von Prozent geschrumpft Zu Jahresbeginn war die nach den USA und China drittgrößte Volkswirtschaft der Welt noch um Prozent gewachsen Der Schwächeanfall wird auch als Rückschlag für Ministerpräsident Shinzo Abe gewertet der das Land mit einem Mix aus billigem Geld und Konjunkturprogramm aus der Flaute bringen will Allerdings wirkte sich die heutige Veröffentlichung auf die Märkten nur wenig aus da Ökonomen mit einem schwächeren zweiten Quartal gerechnet hatten'
38
+ this_book = Book.create title: 'second'
39
+ fill_database[ words2, this_book ]
40
+ end
41
+
42
+ def display_books_with *desired_words
43
+ query = OrientSupport::OrientQuery.new from: Keyword, projection: "expand(in('HasContent'))"
44
+ q = OrientSupport::OrientQuery.new projection: 'expand( $z )'
45
+
46
+ intersects = Array.new
47
+ desired_words.each_with_index do | word, i |
48
+ symbol = ( i+97 ).chr # convert 1 -> 'a'
49
+ query.where = { item: word }
50
+ q.let << { symbol => query }
51
+ intersects << "$#{symbol}"
52
+ end
53
+ q.let << "$z = Intersect(#{intersects.join(', ')}) "
54
+ puts "generated Query:"
55
+ puts q.to_s
56
+ result = Keyword.query_database q, set_from: false
57
+ puts "found books: "
58
+ puts result.map( &:title ).join("; ")
59
+ puts " -- None -- " if result.empty?
60
+ end
61
+ end
62
+
63
+ if $0 == __FILE__
64
+
65
+ require '../config/boot'
66
+
67
+ ActiveOrient::OrientDB.default_server = { user: 'hctw', password: 'hc' }
68
+ ActiveOrient::OrientDB.logger.level = Logger::WARN
69
+ r = ActiveOrient::OrientDB.new database: 'BookTest'
70
+ b= BooksExample.new r, rebuild: true
71
+
72
+ Book = r.open_class :book
73
+ Keyword = r.open_class :keyword
74
+ HC = r.open_class :has_content
75
+
76
+ b.read_samples if Keyword.count.zero?
77
+ b.display_books_with 'Land', 'Quartal'
78
+ end
@@ -0,0 +1,129 @@
1
+ =begin
2
+ Streets Example
3
+
4
+
5
+ We load german cities from wikipedia and parse the document for cities and countries(states).
6
+ Further we load a collection of popular streetnames from a web-side called »strassen-in-deutschland«
7
+
8
+ We define three vertices, State, City and Street.
9
+ These are filled with the data from the web-sides
10
+
11
+ Then we connect the cities through streets just by creating edges.
12
+ At last we print the connected cities.
13
+
14
+ =end
15
+ module DataImport
16
+ def read_german_street_names
17
+ doc = Nokogiri::HTML(open('http://www.strassen-in-deutschland.de/die-haeufigsten-strassennamen-in-deutschland.html'))
18
+ strassen = doc.css('#strassen-in-deutschland_main a' ) # identified via css-inspector in browser
19
+ # search for the css and include only links, then display the text-part
20
+ strassen.children.map( &:to_s )[3..-1] # omit the first three (strassen in deutschland, straßenverzeichnis, straßen)
21
+ end
22
+
23
+ def read_german_cities_from_wikipedia
24
+ # we extract <li> -elements and use the text until "("
25
+ #doc.xpath("//li").at(80)
26
+ # => #<Nokogiri::XML::Element:0x1ba551c name="li" children=[#<Nokogiri::XML::Element:0x1ba533c name="a" attributes=[#<Nokogiri::XML::Attr:0x1ba52d8 name="href" value="/wiki/Angerm%C3%BCnde">, #<Nokogiri::XML::Attr:0x1ba52c4 name="title" value="Angermünde">] children=[#<Nokogiri::XML::Text:0x1ba4c84 "Angermünde">]>, #<Nokogiri::XML::Text:0x1ba4ae0 " (Brandenburg)">]>
27
+
28
+ doc = Nokogiri::HTML(open('https://en.wikipedia.org/wiki/List_of_cities_and_towns_in_Germany'))
29
+ doc.xpath("//li").map{|x| x.text[0 .. x.text.index('(')-2] if x.text.index('(').present? }.compact
30
+ end
31
+
32
+ def read_german_cities_and_states_fom_wikipedia
33
+ doc =
34
+ Nokogiri::HTML(open('https://en.wikipedia.org/wiki/List_of_cities_and_towns_in_Germany'))
35
+ doc.xpath("//li").map do |x|
36
+ if x.text.index('(').present?
37
+ [ x.text[0 .. x.text.index('(')-2] , x.text[ x.text.index('(')+1 .. x.text.index(')')-1] ]
38
+ end
39
+ end.compact
40
+ end
41
+
42
+ end # module
43
+
44
+ class StreetExample
45
+
46
+ include DataImport
47
+ def initialize db, rebuild: true
48
+ if rebuild
49
+ ActiveOrient::OrientDB.logger.progname = "StreetsExample#Initialize"
50
+ db.delete_class :state
51
+ db.delete_class :city
52
+ db.delete_class :street
53
+ db.delete_class :connects
54
+ db.create_vertex_class :state
55
+ db.create_vertex_class :city
56
+ db.create_vertex_class :street
57
+ db.create_edge_class :connects
58
+ ActiveOrient::Model::State.create_property( :name, type: :string, index: :unique )
59
+ ActiveOrient::Model::City.create_properties( { name: { type: :string },
60
+ state: { type: :link, :linked_class => 'State' } }
61
+ ) do
62
+ { citi_idx: :unique }
63
+ end
64
+ ActiveOrient::Model::Street.create_property :name , type: :string, index: :notunique
65
+ ActiveOrient::Model::Connects.create_property :distance, type: :integer, index: :notunique
66
+ ActiveOrient::OrientDB.logger.info { "Vertex- and Edge-Classes rebuilded" }
67
+ end
68
+ end
69
+
70
+
71
+ def read_from_web
72
+ ActiveOrient::OrientDB.logger.progname = "StreetsExample#ReadFromWeb"
73
+ read_german_cities_and_states_fom_wikipedia.each do |city,state|
74
+ state = State.update_or_create( where: { name: state }).first
75
+ City.create name: city, state: state.link
76
+ end
77
+ ActiveOrient::OrientDB.logger.info { "#{City.count} Cities imported from Wikipedia " }
78
+
79
+ cities_rids = City.all.map &:link
80
+ read_german_street_names.each_with_index do |street, i|
81
+ street_record = Street.create name: street
82
+ count = i
83
+ cities = Array.new
84
+ while count < cities_rids.size && cities.size < 5 do
85
+ cities << cities_rids[count]
86
+ count = count + i
87
+ end
88
+ C.create_edge :from => street_record, :to => cities
89
+ end
90
+ ActiveOrient::OrientDB.logger.info { "#{C.count} Edges between Streets and Cities created " }
91
+ end
92
+
93
+ def display_streets_per_state
94
+ State.all.each do |state|
95
+ streets= Street.all.map do |street |
96
+ if street.connects.in.detect{|x| x.state == state }
97
+ street.name + " verbindet " + street.connects.in.map( &:name ).join('; ')
98
+ end
99
+ end.compact
100
+ unless streets.empty?
101
+ puts "..................................."
102
+ puts state.name
103
+ puts "..................................."
104
+ puts streets.join("\n")
105
+ end
106
+ end
107
+ end
108
+ end
109
+
110
+ if $0 == __FILE__
111
+
112
+ require '../config/boot'
113
+ require 'open-uri'
114
+ require 'nokogiri'
115
+
116
+ ActiveOrient::OrientDB.default_server= { user: 'hctw', password: 'hc' }
117
+ r = ActiveOrient::OrientDB.new database: 'StreetTest'
118
+ ActiveOrient::OrientDB.logger.level = Logger::INFO
119
+ s= StreetExample.new r, rebuild: true
120
+
121
+ City = r.open_class :city
122
+ State = r.open_class :state
123
+ Street = r.open_class :street
124
+ C = r.open_class :connects
125
+
126
+ s.read_from_web if City.count.zero?
127
+ s.display_streets_per_state
128
+
129
+ end
@@ -105,20 +105,20 @@ eg . update #link set ...
105
105
  end
106
106
 
107
107
 
108
- def method_missing method, *args, &b
109
- property= orientdb.get_class_properties( classname )['properties'].detect{|x| x.has_value? method.to_s }
110
- puts "method_missing::property"
111
- puts property.inspect
112
- if property.present?
113
- if property['type'] == 'LINKSET'
114
- attributes[method] = OrientSupport::Array.new( self )
115
- else
116
- attributes[method] = ''
117
- end
118
- else
119
- raise NoMethodError
120
- end
121
- end
108
+ # def method_missing method, *args, &b
109
+ # property= orientdb.get_class_properties( classname )['properties'].detect{|x| x.has_value? method.to_s }
110
+ # puts "method_missing::property"
111
+ # puts property.inspect
112
+ # if property.present?
113
+ # if property['type'] == 'LINKSET'
114
+ # attributes[method] = OrientSupport::Array.new( self )
115
+ # else
116
+ # attributes[method] = ''
117
+ # end
118
+ # else
119
+ # raise NoMethodError
120
+ # end
121
+ # end
122
122
  =begin
123
123
  Queries the database and fetches the count of datasets
124
124
 
@@ -149,11 +149,22 @@ and returns the freshly instantiated Object
149
149
  =begin
150
150
  Create a Property in the Schema of the Class
151
151
  :call-seq:
152
- self.create_property field (required) , type: 'string', linked_class: nil
152
+ self.create_property( field (required) , type: 'string', linked_class: nil, index: nil) do
153
+ index
154
+ end
153
155
  =end
154
156
 
155
- def self.create_property field, **keyword_arguments
156
- orientdb.create_property self, field, **keyword_arguments
157
+ def self.get_properties
158
+ object = orientdb.get_class_properties self
159
+ {:properties => object['properties'] , :indexes => object['indexes'] }
160
+ end
161
+
162
+ def self.create_property field, **keyword_arguments, &b
163
+ orientdb.create_property self, field, **keyword_arguments, &b
164
+ end
165
+
166
+ def self.create_properties argument_hash, &b
167
+ orientdb.create_properties self, argument_hash, &b
157
168
  end
158
169
 
159
170
  def self.create_link name, class_name
@@ -167,15 +178,23 @@ Only if the Class inherents from »E«
167
178
  Instantiate a new Edge betwen two Vertices
168
179
 
169
180
  Parameter: unique: (true) In case of an existing Edge just update its Properties.
181
+ The parameters »from« and »to« can take a list of model-records. Then subsequent edges are created.
170
182
  :call-seq:
171
183
  self.create_edge from: , to: , unique: false, attributes:{}
172
184
  =end
173
185
  def self.create_edge **keyword_arguments
174
186
  o=orientdb.nexus_edge self, **keyword_arguments
175
- [:from,:to].each{|y| keyword_arguments[y].reload! }
187
+ [:from,:to].each{|y| keyword_arguments[y].is_a?(Array) ? keyword_arguments[y].each( &:reload! ): keyword_arguments[y].reload! }
176
188
  o # return_value
177
189
  end
178
190
 
191
+ def self.query_database query, set_from: true
192
+ query.from self if set_from && query.is_a?( OrientSupport::OrientQuery ) && query.from.nil?
193
+ sql_cmd = -> (command) { { type: "cmd", language: "sql", command: command } }
194
+ orientdb.execute do
195
+ [ sql_cmd[ query.to_s ] ]
196
+ end
197
+ end
179
198
  def query q
180
199
  a= ActiveOrient::Query.new
181
200
  a.queries << q
@@ -243,7 +262,7 @@ prints a Table with 10 columns.
243
262
  =end
244
263
 
245
264
  def self.get_documents **args , &b
246
- orientdb.get_documents from: classname, **args, &b
265
+ orientdb.get_documents from: self, **args, &b
247
266
 
248
267
  end
249
268
  =begin
@@ -258,7 +277,8 @@ Example:
258
277
 
259
278
  =end
260
279
  def self.where attributes = {}
261
- orientdb.get_documents from: self, where: attributes
280
+ q = OrientSupport::OrientQuery.new where: attributes
281
+ query_database q
262
282
  end
263
283
  =begin
264
284
 
@@ -358,26 +358,33 @@ The parameter o_class can be either a class or a string
358
358
 
359
359
  def nexus_edge o_class , attributes: {}, from:, to:, unique: false
360
360
  logger.progname = "ActiveOrient::OrientDB#NexusEdge"
361
- if unique
362
- wwhere = { out: from.to_orient, in: to.to_orient }.merge(attributes.to_orient)
363
- existing_edge = get_documents( from: o_class, where: wwhere )
364
- if existing_edge.first.is_a?( ActiveOrient::Model )
365
- logger.debug { "reusing edge #{class_name(o_class)} from #{from.to_orient} to #{to.to_orient} " }
366
- return existing_edge.first
361
+ if from.is_a? Array
362
+ from.map{|f| nexus_edge o_class, attributes: attributes, from: f, to: to, unique: unique }
363
+ elsif to.is_a? Array
364
+ to.map{|t| nexus_edge o_class, attributes: attributes, from: from, to: t, unique: unique }
365
+ else
366
+
367
+ if unique
368
+ wwhere = { out: from.to_orient, in: to.to_orient }.merge(attributes.to_orient)
369
+ existing_edge = get_documents( from: o_class, where: wwhere )
370
+ if existing_edge.first.is_a?( ActiveOrient::Model )
371
+ logger.debug { "reusing edge #{class_name(o_class)} from #{from.to_orient} to #{to.to_orient} " }
372
+ return existing_edge.first
373
+ end
374
+ end
375
+ logger.debug { "creating edge #{class_name(o_class)} from #{from.to_orient} to #{to.to_orient} " }
376
+ response= execute( o_class, transaction: false) do
377
+ #[ { type: "cmd", language: 'sql', command: CGI.escapeHTML("create edge #{class_name(o_class)} from #{translate_to_rid[m]} to #{to.to_orient}; ")} ]
378
+ attr_string = attributes.blank? ? "" : "set #{ generate_sql_list attributes.to_orient }"
379
+ [ { type: "cmd", language: 'sql',
380
+ command: "create edge #{class_name(o_class)} from #{from.to_orient} to #{to.to_orient} #{attr_string}"} ]
381
+ end
382
+ if response.is_a?(Array) && response.size == 1
383
+ response.pop # RETURN_VALUE
384
+ else
385
+ response
367
386
  end
368
387
  end
369
- logger.debug { "creating edge #{class_name(o_class)} from #{from.to_orient} to #{to.to_orient} " }
370
- response= execute( o_class, transaction: false) do
371
- #[ { type: "cmd", language: 'sql', command: CGI.escapeHTML("create edge #{class_name(o_class)} from #{translate_to_rid[m]} to #{to.to_roient}; ")} ]
372
- attr_string = attributes.blank? ? "" : "set #{ generate_sql_list attributes.to_orient }"
373
- [ { type: "cmd", language: 'sql',
374
- command: "create edge #{class_name(o_class)} from #{from.to_orient} to #{to.to_orient} #{attr_string}"} ]
375
- end
376
- if response.is_a?(Array) && response.size == 1
377
- response.pop # RETURN_VALUE
378
- else
379
- response
380
- end
381
388
  end
382
389
 
383
390
  =begin
@@ -440,15 +447,49 @@ todo: remove all instances of the class
440
447
  end
441
448
 
442
449
  end
450
+ =begin
451
+ create a single property on class-level.
443
452
 
444
- def create_property o_class, field, type: 'string', linked_class: nil
453
+ supported types: https://orientdb.com/docs/last/SQL-Create-Property.html
454
+
455
+ if index is to be specified, it's defined in the optional block
456
+ create_property(class, field){ :unique | :notunique } --> creates an automatic-Index on the given field
457
+ create_property( class, field){ { »name« => :unique | :notunique | :full_text } } --> creates a manual index
458
+
459
+ =end
460
+ def create_property o_class, field, index: nil, **args
445
461
  logger.progname= 'OrientDB#CreateProperty'
446
- js= if linked_class.nil?
447
- { field => { propertyType: type.upcase } }
448
- else
449
- { field => { propertyType: type.upcase, linkedClass: class_name( linked_class ) } }
450
- end
451
- create_properties( o_class ){ js }
462
+ c= create_properties o_class, {field => args} # translate_property_hash( field, args )
463
+ if index.nil? && block_given?
464
+ index = yield
465
+ end
466
+ if c==1 && index.present?
467
+ if index.is_a?( String ) || index.is_a?( Symbol )
468
+ create_index o_class, name: field, type: index
469
+ elsif index.is_a? Hash
470
+ bez= index.keys.first
471
+ # puts "index: #{index.inspect}"
472
+ # puts "bez: #{bez} ---> #{index.keys.inspect}"
473
+ create_index o_class, name: bez, type: index[bez], on: [ field ]
474
+ end
475
+ end
476
+
477
+ end
478
+
479
+
480
+ def create_index o_class , name:, on: :automatic, type: :unique
481
+
482
+ execute transaction: false do
483
+ c = class_name o_class
484
+ command = if on == :automatic
485
+ "create index #{c}.#{name} #{type.to_s.upcase}"
486
+ elsif on.is_a? Array
487
+ "create index #{name} on #{class_name(o_class)}( #{on.join(', ')}) #{type.to_s.upcase}"
488
+ else
489
+ nil
490
+ end
491
+ [ { type: "cmd", language: 'sql', command: command } ]
492
+ end
452
493
 
453
494
  end
454
495
  ## -----------------------------------------------------------------------------------------
@@ -458,23 +499,54 @@ todo: remove all instances of the class
458
499
  ## create_properties
459
500
  ## get_class_properties
460
501
  ## delete_properties
502
+
461
503
  ##
462
504
  ## -----------------------------------------------------------------------------------------
505
+ private
506
+ def translate_property_hash field, type: nil, linked_class: nil, **args
507
+ type = type.presence || args[:propertyType].presence || args[:property_type]
508
+ linked_class = linked_class.presence || args[:linkedClass]
509
+ if type.present?
510
+ if linked_class.nil?
511
+ { field => { propertyType: type.to_s.upcase } }
512
+ else
513
+ { field => { propertyType: type.to_s.upcase, linkedClass: class_name( linked_class ) } }
514
+ end
515
+ end
516
+
517
+ end
518
+ public
463
519
  =begin
464
520
 
465
- creates properties which are defined as json in the provided block as
466
- create_properties( classname or class ) do
467
- { symbol: { propertyType: 'STRING' },
468
- con_id: { propertyType: 'INTEGER' } ,
469
- details: { propertyType: 'LINK', linkedClass: 'Contracts' }
521
+ creates properties and optional an associated index as defined in the provided block
522
+
523
+ create_properties( classname or class, properties as hash ) { index }
524
+
525
+ The default-case
526
+
527
+ create_properties( con_id: { type: :integer },
528
+ details: { type: :link, linked_class: 'Contracts' } ) do
529
+ contract_idx: :notunique
530
+ end
531
+
532
+ A composite index
533
+
534
+ create_properties( con_id: { type: :integer },
535
+ symbol: { type: :string } ) do
536
+ { name: 'indexname',
537
+ on: [ :con_id , :details ] # default: all specified properties
538
+ type: :notunique # default: :unique
470
539
  }
540
+ end
471
541
 
472
542
  =end
473
- def create_properties o_class
543
+ def create_properties o_class, all_properties, &b
544
+
545
+ all_properties_in_a_hash = HashWithIndifferentAccess.new
546
+ all_properties.each{| field, args | all_properties_in_a_hash.merge! translate_property_hash( field, args ) }
474
547
 
475
548
  begin
476
- all_properties_in_a_hash = yield
477
- if all_properties_in_a_hash.is_a? Hash
549
+ count = if all_properties_in_a_hash.is_a?( Hash )
478
550
  response = @res[ property_uri(class_name(o_class)) ].post all_properties_in_a_hash.to_json
479
551
  if response.code == 201
480
552
  response.body.to_i
@@ -486,12 +558,24 @@ creates properties which are defined as json in the provided block as
486
558
  response = JSON.parse( e.response)['errors'].pop
487
559
  error_message = response['content'].split(':').last
488
560
  # if error_message ~= /Missing linked class/
489
- logger.progname= 'OrientDB#CreateProperty'
561
+ logger.progname= 'OrientDB#CreatePropertes'
490
562
  logger.error { "Properties in #{class_name(o_class)} were NOT created" }
491
563
  logger.error { "Error-code #{response['code']} --> #{response['content'].split(':').last }" }
492
564
  nil
493
565
  end
494
-
566
+ ### index
567
+ if block_given? && count == all_properties_in_a_hash.size
568
+ index = yield
569
+ if index.is_a?( Hash )
570
+ if index.size == 1
571
+ create_index o_class, name: index.keys.first, on: all_properties_in_a_hash.keys, type: index.values.first
572
+ else
573
+ index_hash = HashWithIndifferentAccess.new( type: :unique, on: all_properties_in_a_hash.keys ).merge index
574
+ create_index o_class, index_hash # i [:name], on: index_hash[:on], type: index_hash[:type]
575
+ end
576
+ end
577
+ end
578
+ count # return_value
495
579
  end
496
580
 
497
581
  def delete_property o_class, field
@@ -562,19 +646,22 @@ end
562
646
  Creates an Object in the Database and returns this as ActuveOrient::Model-Instance
563
647
  =end
564
648
 
565
- def create_document o_class, attributes: {}
566
- attributes = yield if attributes.empty? && block_given?
567
- # preallocated_attributes = preallocate_class_properties o_class
568
- # puts "preallocated_attributes: #{o_class} -->#{preallocated_attributes.inspect }"
569
- post_argument = { '@class' => class_name(o_class) }.merge(attributes).to_orient
570
- # puts "post_argument: #{post_argument.inspect}"
571
-
572
- response = @res[ document_uri ].post post_argument.to_json
573
- data= JSON.parse( response.body )
574
- # data = preallocated_attributes.merge data
575
- ActiveOrient::Model.orientdb_class( name: data['@class']).new data
576
- # o_class.new JSON.parse( response.body)
577
- end
649
+ def create_document o_class, attributes: {}
650
+ attributes = yield if attributes.empty? && block_given?
651
+ post_argument = { '@class' => class_name(o_class) }.merge(attributes).to_orient
652
+
653
+ begin
654
+ logger.progname = 'OrientDB#CreateDocument'
655
+ response = @res[ document_uri ].post post_argument.to_json
656
+ data= JSON.parse( response.body )
657
+ ActiveOrient::Model.orientdb_class( name: data['@class']).new data
658
+ rescue RestClient::InternalServerError => e
659
+ response = JSON.parse( e.response)['errors'].pop
660
+ logger.error { response['content'].split(':')[1..-1].join(':') }
661
+ logger.error { "No Object allocated" }
662
+ nil # return_value
663
+ end
664
+ end
578
665
 
579
666
 
580
667
  def delete_document record_id
@@ -633,7 +720,7 @@ end
633
720
  logger.progname = 'OrientDB#count_documents'
634
721
 
635
722
  query = OrientSupport::OrientQuery.new args
636
- query.projection 'COUNT (*)'
723
+ query.projection << 'COUNT (*)'
637
724
  result = get_documents raw: true, query: query
638
725
 
639
726
  result.first['COUNT']
@@ -824,16 +911,16 @@ structure of the provided block:
824
911
  def execute classname = 'Myquery', transaction: true
825
912
  batch = { transaction: transaction, operations: yield }
826
913
  unless batch[:operations].blank?
827
- # puts "batch_uri: #{@res[batch_uri]}"
828
- # puts "post: #{batch.to_json}"
914
+ # puts "post: #{batch.to_json}"
829
915
  response = @res[ batch_uri ].post batch.to_json
916
+ # puts "response: #{JSON.parse(response.body)['result'].inspect}"
830
917
  if response.code == 200
831
918
  if response.body['result'].present?
832
919
  result= JSON.parse(response.body)['result']
833
920
  result.map do |x|
834
921
  if x.is_a? Hash
835
922
  if x.has_key?('@class')
836
- ActiveOrient::Model.orientdb_class( name: x['@class']).new x
923
+ ActiveOrient::Model.orientdb_class( name: x['@class']).new x
837
924
  elsif x.has_key?( 'value' )
838
925
  x['value']
839
926
  else
@@ -20,6 +20,8 @@ class String
20
20
  self
21
21
  end
22
22
  end
23
+ ## this enables the universal use of an rid-string as pseudonym for a ActiveOrient::Model
24
+ alias :reload! from_orient
23
25
  end
24
26
  class NilClass
25
27
  def to_orient
@@ -152,9 +154,23 @@ Used by update and select
152
154
 
153
155
  def initialize **args
154
156
  @projection = []
155
- @misc= []
157
+ @misc = []
158
+ @let = []
159
+ @where = []
160
+ @order = []
156
161
  args.each do | k,v|
157
- self.send k, v
162
+ case k
163
+ when :projection
164
+ @projection << v
165
+ when :let
166
+ @let << v
167
+ when :order
168
+ @order << v
169
+ when :where
170
+ @where << v
171
+ else
172
+ self.send k, v
173
+ end
158
174
  end
159
175
  end
160
176
 
@@ -171,8 +187,10 @@ Used by update and select
171
187
  end
172
188
 
173
189
  def compose
174
- [ "select", projection, from, where , subquery, misc, order , group_by, unwind, skip ].compact.join(' ')
190
+ [ "select", projection_s, from, let_s, where_s , subquery, misc, order_s , group_by, unwind, skip ].compact.join(' ')
175
191
  end
192
+
193
+ alias :to_s :compose
176
194
  =begin
177
195
  from can either be a Databaseclass to operate on or a Subquery providing data to query further
178
196
  =end
@@ -188,17 +206,14 @@ from can either be a Databaseclass to operate on or a Subquery providing data to
188
206
  when Symbol
189
207
  arg
190
208
  when OrientQuery
191
- nil # don't set @database
209
+ arg
192
210
  end
193
- @from = arg
211
+ compose # return the complete query
194
212
  else # read from
195
- "from " << if @from.is_a?( OrientQuery )
196
- "( #{@from.compose} )"
197
- else
198
- @database.to_s
213
+ "from " << @database.to_s unless @database.nil?
199
214
  end
200
215
  end
201
- end
216
+ alias :from= :from
202
217
  def database_class= arg
203
218
  @database = arg if @database.present?
204
219
  if @from.is_a? OrientQuery
@@ -225,23 +240,32 @@ where: "r > 9" --> where r > 9
225
240
  where: {a: 9, b: 's'} --> where a = 9 and b = 's'
226
241
  where:[{ a: 2} , 'b > 3',{ c: 'ufz' }] --> where a = 2 and b > 3 and c = 'ufz'
227
242
  =end
228
- def where *arg
229
- @where= compose_where(*arg) unless arg.empty?
230
- @where # return_value
243
+ attr_accessor :where
244
+ def where_s
245
+ compose_where @where
231
246
  end
232
247
 
233
- def let *arg
234
- # SELECT FROM Profile
235
- # LET $city = address.city
236
- # WHERE $city.name like '%Saint%"' AND
237
- # ( $city.country.name = 'Italy' OR $city.country.name = 'France' )
238
- puts "OrientSupport::OrientQuery@let is not implementated"
248
+
249
+ attr_accessor :let
250
+ def let_s
251
+ unless @let.empty?
252
+ "let " << @let.map do |s|
253
+ case s
254
+ when Hash
255
+ s.map{ |x,y| "$#{x} = ( #{y} )"}.join( ', ')
256
+ when Array
257
+ s.join(', ')
258
+ else
259
+ s
260
+ end
261
+ end.join(', ')
262
+ end
239
263
  end
240
- def distinct d=nil
241
- if d.present?
264
+
265
+ def distinct d
242
266
  @projection << case d
243
- when String
244
- "distinct( #{d} )"
267
+ when String, Symbol
268
+ "distinct( #{d.to_s} )"
245
269
  when Array
246
270
  "distinct( #{d.first} ) as #{d.last}"
247
271
  when Hash
@@ -249,13 +273,15 @@ where:[{ a: 2} , 'b > 3',{ c: 'ufz' }] --> where a = 2 and b > 3 and c = 'ufz'
249
273
  else
250
274
  ""
251
275
  end
252
- end
253
- @projection.join(' ,') #return_value
276
+ compose # return the hole query
254
277
  end
255
- def projection s=nil
256
- if s.present?
278
+ alias :distinct= :distinct
279
+
280
+ attr_accessor :projection
281
+ def projection_s
257
282
 
258
- @projection << case s
283
+ @projection.map do | s |
284
+ case s
259
285
  when Hash
260
286
  s.map{ |x,y| "#{x} as #{y}"}.join( ', ')
261
287
  when Array
@@ -263,11 +289,11 @@ where:[{ a: 2} , 'b > 3',{ c: 'ufz' }] --> where a = 2 and b > 3 and c = 'ufz'
263
289
  else
264
290
  s
265
291
  end
292
+ end.join( ', ' )
266
293
 
267
- end
268
- @projection.join(', ')
269
294
  end
270
295
 
296
+
271
297
  # def where= w
272
298
 
273
299
  # end
@@ -286,19 +312,25 @@ where:[{ a: 2} , 'b > 3',{ c: 'ufz' }] --> where a = 2 and b > 3 and c = 'ufz'
286
312
 
287
313
  def skip n=nil
288
314
  @skip= n if n.present?
289
- "skip #{n}" if @skip.present?
315
+ "skip #{@skip}" if @skip.present?
290
316
  end
291
317
 
292
- def order o=nil
293
- @order_string = "order by " << case o
294
- when Hash
295
- o.map{ |x,y| "#{x} #{y}" }.join( " " )
296
- when Array
297
- o.map{ |x| "#{x} asc"}.join( " " )
318
+ attr_accessor :order
319
+
320
+ def order_s
321
+ unless @order.empty?
322
+ # the [@order] is nessesary to enable query.order= "..." oder query.order= { a: :b }
323
+ "order by " << [@order].flatten.map do | o |
324
+ case o
325
+ when Hash
326
+ o.map{ |x,y| "#{x} #{y}" }.join( " " )
327
+ else
328
+ o.to_s
329
+ end # case
330
+ end.join(', ')
298
331
  else
299
- o.to_s
300
- end if o.present?
301
- @order_string
332
+ ''
333
+ end
302
334
  end
303
335
  # misc_string = if skip > 0 && limit > 0
304
336
  # " skip: #{skip} "
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active-orient
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.3'
4
+ version: '0.4'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hartmut Bischoff
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2015-08-18 00:00:00.000000000 Z
11
+ date: 2015-09-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -58,14 +58,14 @@ dependencies:
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: 2.0.0.alpha
61
+ version: '2.0'
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: 2.0.0.alpha
68
+ version: '2.0'
69
69
  description: Persistent ORM for OrientDB, based on ActiveModel
70
70
  email:
71
71
  - topofocus@gmail.com
@@ -83,6 +83,8 @@ files:
83
83
  - active-orient.gemspec
84
84
  - config/boot.rb
85
85
  - config/connect.yml
86
+ - examples/books.rb
87
+ - examples/streets.rb
86
88
  - lib/active-orient.rb
87
89
  - lib/base.rb
88
90
  - lib/base_properties.rb