active-orient 0.4 → 0.80
Sign up to get free protection for your applications and to get access to all the features.
- 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
|