active-orient 0.4 → 0.80
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.
- checksums.yaml +5 -5
- data/.gitignore +1 -0
- data/.graphs.txt.swp +0 -0
- data/Gemfile +9 -5
- data/Guardfile +12 -4
- data/README.md +70 -281
- data/VERSION +1 -1
- data/active-orient.gemspec +9 -7
- data/bin/active-orient-0.6.gem +0 -0
- data/bin/active-orient-console +97 -0
- data/changelog.md +60 -0
- data/config/boot.rb +70 -17
- data/config/config.yml +10 -0
- data/config/connect.yml +11 -6
- data/examples/books.rb +154 -65
- data/examples/streets.rb +89 -85
- data/graphs.txt +70 -0
- data/lib/active-orient.rb +78 -6
- data/lib/base.rb +266 -168
- data/lib/base_properties.rb +76 -65
- data/lib/class_utils.rb +187 -0
- data/lib/database_utils.rb +99 -0
- data/lib/init.rb +80 -0
- data/lib/java-api.rb +442 -0
- data/lib/jdbc.rb +211 -0
- data/lib/model/custom.rb +29 -0
- data/lib/model/e.rb +6 -0
- data/lib/model/edge.rb +114 -0
- data/lib/model/model.rb +134 -0
- data/lib/model/the_class.rb +657 -0
- data/lib/model/the_record.rb +313 -0
- data/lib/model/vertex.rb +371 -0
- data/lib/orientdb_private.rb +48 -0
- data/lib/other.rb +423 -0
- data/lib/railtie.rb +68 -0
- data/lib/rest/change.rb +150 -0
- data/lib/rest/create.rb +287 -0
- data/lib/rest/delete.rb +150 -0
- data/lib/rest/operations.rb +222 -0
- data/lib/rest/read.rb +189 -0
- data/lib/rest/rest.rb +120 -0
- data/lib/rest_disabled.rb +24 -0
- data/lib/support/conversions.rb +42 -0
- data/lib/support/default_formatter.rb +7 -0
- data/lib/support/errors.rb +41 -0
- data/lib/support/logging.rb +38 -0
- data/lib/support/orient.rb +305 -0
- data/lib/support/orientquery.rb +647 -0
- data/lib/support/query.rb +92 -0
- data/rails.md +154 -0
- data/rails/activeorient.rb +32 -0
- data/rails/config.yml +10 -0
- data/rails/connect.yml +17 -0
- metadata +89 -30
- data/lib/model.rb +0 -461
- data/lib/orient.rb +0 -98
- data/lib/query.rb +0 -88
- data/lib/rest.rb +0 -1036
- data/lib/support.rb +0 -347
- data/test.rb +0 -4
- data/usecase.md +0 -91
data/lib/jdbc.rb
ADDED
@@ -0,0 +1,211 @@
|
|
1
|
+
require 'orientdb'
|
2
|
+
require_relative "database_utils.rb" #common methods without rest.specific content
|
3
|
+
require_relative "class_utils.rb" #common methods without rest.specific content
|
4
|
+
require_relative "orientdb_private.rb"
|
5
|
+
|
6
|
+
|
7
|
+
module ActiveOrient
|
8
|
+
|
9
|
+
|
10
|
+
class API
|
11
|
+
include OrientSupport::Support
|
12
|
+
include DatabaseUtils
|
13
|
+
include ClassUtils
|
14
|
+
include OrientDbPrivate
|
15
|
+
include OrientDB
|
16
|
+
|
17
|
+
mattr_accessor :logger # borrowed from active_support
|
18
|
+
mattr_accessor :default_server
|
19
|
+
attr_reader :database # Used to read the working database
|
20
|
+
|
21
|
+
#### INITIALIZATION ####
|
22
|
+
|
23
|
+
|
24
|
+
def initialize database: nil, connect: true, preallocate: true
|
25
|
+
self.logger = Logger.new('/dev/stdout') unless logger.present?
|
26
|
+
self.default_server = {
|
27
|
+
:server => 'localhost',
|
28
|
+
:port => 2480,
|
29
|
+
:protocol => 'http',
|
30
|
+
:user => 'root',
|
31
|
+
:password => 'root',
|
32
|
+
:database => 'temp'
|
33
|
+
}.merge default_server.presence || {}
|
34
|
+
@database = database || default_server[:database]
|
35
|
+
@all_classes=[]
|
36
|
+
#puts ["remote:#{default_server[:server]}/#{@database}",
|
37
|
+
# default_server[:user], default_server[:password] ]
|
38
|
+
connect() if connect
|
39
|
+
# @db = DocumentDatabase.connect("remote:#{default_server[:server]}/#{@database}",
|
40
|
+
# default_server[:user], default_server[:password] )
|
41
|
+
ActiveOrient::Model.api = self
|
42
|
+
preallocate_classes if preallocate
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
def db
|
47
|
+
@db
|
48
|
+
end
|
49
|
+
|
50
|
+
# Used for the connection on the server
|
51
|
+
#
|
52
|
+
|
53
|
+
# Used to connect to the database
|
54
|
+
|
55
|
+
def connect
|
56
|
+
|
57
|
+
@db = DocumentDatabase.connect("remote:#{default_server[:server]}/#{@database}",
|
58
|
+
default_server[:user], default_server[:password] )
|
59
|
+
@classes = get_database_classes
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
|
64
|
+
def get_classes *attributes
|
65
|
+
classes= @db.metadata.schema.classes.map{|x| { 'name' => x.name , 'superClass' => x.get_super_class.nil? ? '': x.get_super_class.name } }
|
66
|
+
unless attributes.empty?
|
67
|
+
classes.map{|y| y.select{|v,_| attributes.include?(v)}}
|
68
|
+
else
|
69
|
+
classes
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
def create_classes classes , &b
|
76
|
+
consts = allocate_classes_in_ruby( classes , &b )
|
77
|
+
|
78
|
+
all_classes = consts.is_a?( Array) ? consts.flatten : [consts]
|
79
|
+
get_database_classes(requery: true)
|
80
|
+
selected_classes = all_classes.map do | this_class |
|
81
|
+
this_class unless get_database_classes(requery: false).include?( this_class.ref_name ) rescue nil
|
82
|
+
end.compact.uniq
|
83
|
+
command= selected_classes.map do | database_class |
|
84
|
+
## improper initialized ActiveOrient::Model-classes lack a ref_name class-variable
|
85
|
+
next if database_class.ref_name.blank?
|
86
|
+
c = if database_class.superclass == ActiveOrient::Model || database_class.superclass.ref_name.blank?
|
87
|
+
puts "CREATE CLASS #{database_class.ref_name} "
|
88
|
+
OClassImpl.create @db, database_class.ref_name
|
89
|
+
else
|
90
|
+
puts "CREATE CLASS #{database_class.ref_name} EXTENDS #{database_class.superclass.ref_name}"
|
91
|
+
OClassImpl.create @db, superClass: database_class.ref_name
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# update the internal class hierarchy
|
96
|
+
get_database_classes requery: true
|
97
|
+
# return all allocated classes, no matter whether they had to be created in the DB or not.
|
98
|
+
# keep the format of the input-parameter
|
99
|
+
consts.shift if block_given? && consts.is_a?( Array) # remove the first element
|
100
|
+
# remove traces of superclass-allocations
|
101
|
+
if classes.is_a? Hash
|
102
|
+
consts = Hash[ consts ]
|
103
|
+
consts.each_key{ |x| consts[x].delete_if{|y| y == x} if consts[x].is_a? Array }
|
104
|
+
end
|
105
|
+
consts
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
|
110
|
+
def delete_class o_class
|
111
|
+
@db.schema.drop_class classname(o_class)
|
112
|
+
get_database_classes requery: true
|
113
|
+
end
|
114
|
+
=begin
|
115
|
+
Creates properties and optional an associated index as defined in the provided block
|
116
|
+
create_properties(classname or class, properties as hash){index}
|
117
|
+
|
118
|
+
The default-case
|
119
|
+
create_properties(:my_high_sophisticated_database_class,
|
120
|
+
con_id: {type: :integer},
|
121
|
+
details: {type: :link, linked_class: 'Contracts'}) do
|
122
|
+
contract_idx: :notunique
|
123
|
+
end
|
124
|
+
|
125
|
+
A composite index
|
126
|
+
create_properties(:my_high_sophisticated_database_class,
|
127
|
+
con_id: {type: :integer},
|
128
|
+
symbol: {type: :string}) do
|
129
|
+
{name: 'indexname',
|
130
|
+
on: [:con_id, :details] # default: all specified properties
|
131
|
+
type: :notunique # default: :unique
|
132
|
+
}
|
133
|
+
end
|
134
|
+
=end
|
135
|
+
|
136
|
+
def create_properties o_class, **all_properties, &b
|
137
|
+
logger.progname = 'JavaApi#CreateProperties'
|
138
|
+
ap = all_properties
|
139
|
+
created_properties = ap.map do |property, specification |
|
140
|
+
puts "specification: #{specification.inspect}"
|
141
|
+
field_type = ( specification.is_a?( Hash) ? specification[:type] : specification ).downcase.to_sym rescue :string
|
142
|
+
the_other_class = specification.is_a?(Hash) ? specification[:other_class] : nil
|
143
|
+
other_class = if the_other_class.present?
|
144
|
+
@db.get_class( the_other_class)
|
145
|
+
end
|
146
|
+
index = ap.is_a?(Hash) ? ap[:index] : nil
|
147
|
+
if other_class.present?
|
148
|
+
@db.get_class(classname(o_class)).add property,[ field_type, other_class ], { :index => index }
|
149
|
+
else
|
150
|
+
@db.get_class(classname(o_class)).add property, field_type, { :index => index }
|
151
|
+
end
|
152
|
+
end
|
153
|
+
if block_given?
|
154
|
+
attr = yield
|
155
|
+
index_parameters = case attr
|
156
|
+
when String, Symbol
|
157
|
+
{ name: attr }
|
158
|
+
when Hash
|
159
|
+
{ name: attr.keys.first , type: attr.values.first, on: all_properties.keys.map(&:to_s) }
|
160
|
+
else
|
161
|
+
nil
|
162
|
+
end
|
163
|
+
create_index o_class, **index_parameters unless index_parameters.blank?
|
164
|
+
end
|
165
|
+
created_properties.size # return_value
|
166
|
+
|
167
|
+
end
|
168
|
+
|
169
|
+
def get_properties o_class
|
170
|
+
@db.get_class(classname(o_class)).propertiesMap
|
171
|
+
end
|
172
|
+
|
173
|
+
|
174
|
+
|
175
|
+
def create_index o_class, name:, on: :automatic, type: :unique
|
176
|
+
logger.progname = 'JavaApi#CreateIndex'
|
177
|
+
begin
|
178
|
+
c = @db.get_class( classname( o_class ))
|
179
|
+
index = if on == :automatic
|
180
|
+
nil # not implemented
|
181
|
+
elsif on.is_a? Array
|
182
|
+
c.createIndex name.to_s, INDEX_TYPES[type], *on
|
183
|
+
else
|
184
|
+
c.createIndex name.to_s, INDEX_TYPES[type], on
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def create_record o_class, attributes: {}
|
190
|
+
logger.progname = 'HavaApi#CreateRecord'
|
191
|
+
attributes = yield if attributes.empty? && block_given?
|
192
|
+
new_record = insert_document( o_class, attributes.to_orient )
|
193
|
+
|
194
|
+
|
195
|
+
end
|
196
|
+
alias create_document create_record
|
197
|
+
|
198
|
+
def insert_document o_class, attributes
|
199
|
+
d = Document.create @db, classname(o_class), **attributes
|
200
|
+
d.save
|
201
|
+
ActiveOrient::Model.get_model_class(classname(o_class)).new attributes.merge( { "@rid" => d.rid,
|
202
|
+
"@version" => d.version,
|
203
|
+
"@type" => 'd',
|
204
|
+
"@class" => classname(o_class) } )
|
205
|
+
|
206
|
+
|
207
|
+
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
data/lib/model/custom.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
module CustomClass
|
2
|
+
=begin
|
3
|
+
The REST-Interface does not work with "select from SomeClass where a_property like 'pattern%' "
|
4
|
+
|
5
|
+
This is rewritten as
|
6
|
+
|
7
|
+
SomeClass.like "name = D*", order: 'asc'
|
8
|
+
|
9
|
+
The order-argument is optional, "asc" is the default.
|
10
|
+
This Primitiv-Version only accepts the wildcards "*" and "%" at the end of the seach-string.
|
11
|
+
The Wildcard can be omitted.
|
12
|
+
|
13
|
+
The method does not accept further arguments.
|
14
|
+
=end
|
15
|
+
def like operation, order: 'asc'
|
16
|
+
# remove all spaces and split the resulting word
|
17
|
+
case operation
|
18
|
+
when Hash
|
19
|
+
p,s = operation.keys.first, operation.values.first
|
20
|
+
else
|
21
|
+
p, s = operation.gsub(/\s+/, "").split("=")
|
22
|
+
end
|
23
|
+
if ["%","*"].include?(s[-1])
|
24
|
+
s.chop!
|
25
|
+
end
|
26
|
+
|
27
|
+
query( where: { "#{p}.left(#{s.length})" => s } ,order: { p => order }).execute
|
28
|
+
end
|
29
|
+
end
|
data/lib/model/e.rb
ADDED
data/lib/model/edge.rb
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
class E < ActiveOrient::Model
|
2
|
+
## class methods
|
3
|
+
class << self
|
4
|
+
def naming_convention name=nil
|
5
|
+
name.present? ? name.upcase : ref_name.upcase
|
6
|
+
end
|
7
|
+
|
8
|
+
=begin
|
9
|
+
Establish constrains on Edges
|
10
|
+
|
11
|
+
After applying this method Edges are uniq!
|
12
|
+
|
13
|
+
Creates individual indices for child-classes if applied to the class itself.
|
14
|
+
=end
|
15
|
+
def uniq_index
|
16
|
+
create_property :in, type: :link, linked_class: :V
|
17
|
+
create_property :out, type: :link, linked_class: :V
|
18
|
+
create_index "#{ref_name}_idx", on: [ :in, :out ]
|
19
|
+
end
|
20
|
+
|
21
|
+
=begin
|
22
|
+
Instantiate a new Edge between two Vertices
|
23
|
+
|
24
|
+
Properties can be placed using the :set-directive or simply by adding key: value- parameter-pairs
|
25
|
+
|
26
|
+
if the creation of an edged is not possible, due to constrains (uniq_index), the already
|
27
|
+
connecting edge is returned
|
28
|
+
|
29
|
+
the method is thread safe, if transaction and update_cache are set to false
|
30
|
+
=end
|
31
|
+
def create from:, to: , set: {}, transaction: false, update_cache: false, **attributes
|
32
|
+
return nil if from.blank? || to.blank?
|
33
|
+
set.merge!(attributes)
|
34
|
+
content = set.empty? ? "" : "content #{set.to_orient.to_json}"
|
35
|
+
statement = "CREATE EDGE #{ref_name} from #{from.to_or} to #{to.to_or} #{content}"
|
36
|
+
transaction = true if [:fire, :complete, :run].include?(transaction)
|
37
|
+
ir= db.execute( transaction: transaction, process_error: false ){ statement }
|
38
|
+
if update_cache
|
39
|
+
from.reload! # get last version
|
40
|
+
to.is_a?(Array)? to.each( &:reload! ) : to.reload!
|
41
|
+
end
|
42
|
+
to.is_a?(Array) ? ir : ir.first # return the plain edge, if only one is created
|
43
|
+
rescue RestClient::InternalServerError => e
|
44
|
+
sentence= JSON.parse( e.response)['errors'].last['content']
|
45
|
+
if sentence =~ /found duplicated key/
|
46
|
+
ref_rid = sentence.split.last.expand # return expanded rid
|
47
|
+
else
|
48
|
+
raise
|
49
|
+
end
|
50
|
+
rescue ArgumentError => e
|
51
|
+
logger.error{ "wrong parameters #{keyword_arguments} \n\t\t required: from: , to: , attributes:\n\t\t Edge is NOT created"}
|
52
|
+
end
|
53
|
+
|
54
|
+
=begin
|
55
|
+
Fires a "delete edge" command to the database.
|
56
|
+
|
57
|
+
|
58
|
+
The where statement can be empty ( "" or {}"), then all edges are removed
|
59
|
+
|
60
|
+
The rid-cache is resetted
|
61
|
+
|
62
|
+
|
63
|
+
to_do: Implement :all=> true directive
|
64
|
+
support from: , to: syntax
|
65
|
+
|
66
|
+
:call-seq:
|
67
|
+
delete where:
|
68
|
+
=end
|
69
|
+
def delete where:
|
70
|
+
|
71
|
+
db.execute { "delete edge #{ref_name} #{db.compose_where(where)}" }
|
72
|
+
reset_rid_store
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
def connect dir= "-" , **args # arguments: direction: :both,
|
78
|
+
# count: 1,
|
79
|
+
# # as: nil
|
80
|
+
|
81
|
+
direction = case dir
|
82
|
+
when "-"
|
83
|
+
:both
|
84
|
+
when '->'
|
85
|
+
:out
|
86
|
+
when '<-'
|
87
|
+
:in
|
88
|
+
when Symbol
|
89
|
+
dir
|
90
|
+
end
|
91
|
+
args[:direction] ||= direction
|
92
|
+
|
93
|
+
|
94
|
+
OrientSupport::MatchConnection.new self, **args
|
95
|
+
end
|
96
|
+
|
97
|
+
end # class methods
|
98
|
+
|
99
|
+
### instance methods ###
|
100
|
+
|
101
|
+
=begin
|
102
|
+
Deletes the actual ActiveOrient::Model-Edge-Object
|
103
|
+
|
104
|
+
=end
|
105
|
+
|
106
|
+
def delete
|
107
|
+
db.execute{ "delete edge #{ref_name} #{rrid}" }
|
108
|
+
end
|
109
|
+
def to_human
|
110
|
+
displayed_attributes = attributes.reject{|k,_| [:in, :out].include?(k) }
|
111
|
+
"<#{self.class.to_s.demodulize}[#{rrid}] :.: #{ attributes[:out].rid}->#{displayed_attributes.to_human}->#{attributes[:in].rid}>"
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
data/lib/model/model.rb
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
require_relative "the_class.rb"
|
2
|
+
require_relative "the_record.rb"
|
3
|
+
require_relative "custom.rb"
|
4
|
+
module ActiveOrient
|
5
|
+
class Model < ActiveOrient::Base
|
6
|
+
|
7
|
+
include BaseProperties
|
8
|
+
include ModelRecord # For objects (file: lib/record.rb)
|
9
|
+
extend CustomClass # for customized class-methods aka like
|
10
|
+
extend ModelClass # For classes
|
11
|
+
|
12
|
+
=begin
|
13
|
+
Either retrieves the object from the rid_store or loads it from the DB.
|
14
|
+
|
15
|
+
Example:
|
16
|
+
ActiveOrient::Model.autoload_object "#00:00"
|
17
|
+
|
18
|
+
|
19
|
+
The rid_store is updated!
|
20
|
+
|
21
|
+
_Todo:_ fetch for version in the db and load the object only if a change is detected
|
22
|
+
|
23
|
+
_Note:_ This function is not located in ModelClass since it needs to use @@rid_store
|
24
|
+
=end
|
25
|
+
|
26
|
+
def self.autoload_object rid
|
27
|
+
rid = rid[1..-1] if rid[0]=='#'
|
28
|
+
if rid.rid?
|
29
|
+
if @@rid_store[rid].present?
|
30
|
+
@@rid_store[rid] # return_value
|
31
|
+
else
|
32
|
+
get(rid)
|
33
|
+
end
|
34
|
+
else
|
35
|
+
logger.progname = "ActiveOrient::Model#AutoloadObject"
|
36
|
+
logger.info{"#{rid} is not a valid rid."}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
## used for active-model-compatibility
|
41
|
+
def persisted?
|
42
|
+
true
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
|
47
|
+
=begin
|
48
|
+
Based on the parameter rid (as "#{a}:{b}" or "{a}:{b}") the cached value is used if found.
|
49
|
+
Otherwise the provided Block is executed, which is responsible for the allocation of a new dataset
|
50
|
+
|
51
|
+
i.e.
|
52
|
+
ActiveOrient::Model.use_or_allocated my_rid do
|
53
|
+
ActiveOrient::Model.orientdb_class(name: raw_data['@class']).new raw_data
|
54
|
+
end
|
55
|
+
|
56
|
+
=end
|
57
|
+
|
58
|
+
def self.use_or_allocate rid
|
59
|
+
cached_obj = get_rid( rid )
|
60
|
+
if cached_obj.present?
|
61
|
+
cached_obj
|
62
|
+
else
|
63
|
+
yield
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
def self._to_partial_path #:nodoc:
|
69
|
+
@_to_partial_path ||= begin
|
70
|
+
element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(name))
|
71
|
+
collection = ActiveSupport::Inflector.tableize(name)
|
72
|
+
"#{collection}/#{element}".freeze
|
73
|
+
end
|
74
|
+
end
|
75
|
+
## to prevent errors when calling to_a
|
76
|
+
def to_ary # :nodoc:
|
77
|
+
attributes.to_a
|
78
|
+
end
|
79
|
+
|
80
|
+
def document # :nodoc:
|
81
|
+
@d
|
82
|
+
end
|
83
|
+
|
84
|
+
=begin
|
85
|
+
Deletes the database class and removes the ruby-class
|
86
|
+
=end
|
87
|
+
def self.delete_class what= :all
|
88
|
+
orientdb.delete_class( self ) if what == :all # remove the database-class
|
89
|
+
## namespace is defined in config/boot
|
90
|
+
ns = namespace.to_s == 'Object' ? "" : namespace.to_s
|
91
|
+
ns_found = -> ( a_class ) do
|
92
|
+
to_compare = a_class.to_s.split(':')
|
93
|
+
if ns == "" && to_compare.size == 1
|
94
|
+
true
|
95
|
+
elsif to_compare.first == ns
|
96
|
+
true
|
97
|
+
else
|
98
|
+
false
|
99
|
+
end
|
100
|
+
end
|
101
|
+
self.allocated_classes.delete_if{|x,y| x == self.ref_name && ns_found[y]} if allocated_classes.is_a?(Hash)
|
102
|
+
namespace.send(:remove_const, naming_convention.to_sym) if namespace &.send( :const_defined?, naming_convention)
|
103
|
+
end
|
104
|
+
|
105
|
+
# provides an unique accessor on the Class
|
106
|
+
# works with a class-variable, its unique through all Subclasses
|
107
|
+
mattr_accessor :orientdb # points to the instance of the REST-DB-Client used for Administration
|
108
|
+
# i.e. creation and deleting of classes and databases
|
109
|
+
mattr_accessor :db # points to the instance of the Client used for Database-Queries
|
110
|
+
mattr_accessor :api
|
111
|
+
# mattr_accessor :logger ... already inherented from ::Base
|
112
|
+
mattr_accessor :namespace # Namespace in which Model records are initialized, a constant ( defined in config.yml )
|
113
|
+
mattr_accessor :model_dir # path to model-files
|
114
|
+
mattr_accessor :keep_models_without_file
|
115
|
+
mattr_accessor :allocated_classes
|
116
|
+
|
117
|
+
# mattr_accessor :ref_name
|
118
|
+
# Used to read the metadata
|
119
|
+
attr_reader :metadata
|
120
|
+
|
121
|
+
# provides an accessor at class level
|
122
|
+
# (unique on all instances)
|
123
|
+
class << self
|
124
|
+
attr_accessor :ref_name
|
125
|
+
attr_accessor :abstract
|
126
|
+
|
127
|
+
def to_or
|
128
|
+
ref_name.to_or
|
129
|
+
end
|
130
|
+
|
131
|
+
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|