square-arel 2.0.9.20110222133018
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +26 -0
- data/History.txt +105 -0
- data/MIT-LICENSE.txt +20 -0
- data/Manifest.txt +124 -0
- data/README.markdown +94 -0
- data/Rakefile +20 -0
- data/lib/arel.rb +39 -0
- data/lib/arel/attributes.rb +20 -0
- data/lib/arel/attributes/attribute.rb +18 -0
- data/lib/arel/compatibility/wheres.rb +33 -0
- data/lib/arel/crud.rb +37 -0
- data/lib/arel/delete_manager.rb +18 -0
- data/lib/arel/deprecated.rb +4 -0
- data/lib/arel/expression.rb +4 -0
- data/lib/arel/expressions.rb +23 -0
- data/lib/arel/insert_manager.rb +34 -0
- data/lib/arel/nodes.rb +53 -0
- data/lib/arel/nodes/and.rb +6 -0
- data/lib/arel/nodes/as.rb +6 -0
- data/lib/arel/nodes/assignment.rb +6 -0
- data/lib/arel/nodes/avg.rb +6 -0
- data/lib/arel/nodes/between.rb +6 -0
- data/lib/arel/nodes/binary.rb +12 -0
- data/lib/arel/nodes/count.rb +13 -0
- data/lib/arel/nodes/delete_statement.rb +19 -0
- data/lib/arel/nodes/does_not_match.rb +6 -0
- data/lib/arel/nodes/equality.rb +9 -0
- data/lib/arel/nodes/except.rb +7 -0
- data/lib/arel/nodes/exists.rb +7 -0
- data/lib/arel/nodes/function.rb +18 -0
- data/lib/arel/nodes/greater_than.rb +6 -0
- data/lib/arel/nodes/greater_than_or_equal.rb +6 -0
- data/lib/arel/nodes/group.rb +6 -0
- data/lib/arel/nodes/grouping.rb +6 -0
- data/lib/arel/nodes/having.rb +6 -0
- data/lib/arel/nodes/in.rb +6 -0
- data/lib/arel/nodes/inner_join.rb +6 -0
- data/lib/arel/nodes/insert_statement.rb +19 -0
- data/lib/arel/nodes/intersect.rb +7 -0
- data/lib/arel/nodes/join.rb +13 -0
- data/lib/arel/nodes/less_than.rb +6 -0
- data/lib/arel/nodes/less_than_or_equal.rb +6 -0
- data/lib/arel/nodes/limit.rb +7 -0
- data/lib/arel/nodes/lock.rb +6 -0
- data/lib/arel/nodes/matches.rb +6 -0
- data/lib/arel/nodes/max.rb +6 -0
- data/lib/arel/nodes/min.rb +6 -0
- data/lib/arel/nodes/node.rb +44 -0
- data/lib/arel/nodes/not.rb +6 -0
- data/lib/arel/nodes/not_equal.rb +6 -0
- data/lib/arel/nodes/not_in.rb +6 -0
- data/lib/arel/nodes/offset.rb +7 -0
- data/lib/arel/nodes/on.rb +6 -0
- data/lib/arel/nodes/or.rb +6 -0
- data/lib/arel/nodes/ordering.rb +20 -0
- data/lib/arel/nodes/outer_join.rb +6 -0
- data/lib/arel/nodes/select_core.rb +26 -0
- data/lib/arel/nodes/select_statement.rb +22 -0
- data/lib/arel/nodes/sql_literal.rb +8 -0
- data/lib/arel/nodes/string_join.rb +11 -0
- data/lib/arel/nodes/sum.rb +6 -0
- data/lib/arel/nodes/table_alias.rb +13 -0
- data/lib/arel/nodes/top.rb +6 -0
- data/lib/arel/nodes/unary.rb +11 -0
- data/lib/arel/nodes/union.rb +7 -0
- data/lib/arel/nodes/union_all.rb +7 -0
- data/lib/arel/nodes/unqualified_column.rb +16 -0
- data/lib/arel/nodes/update_statement.rb +21 -0
- data/lib/arel/nodes/values.rb +14 -0
- data/lib/arel/predications.rb +183 -0
- data/lib/arel/relation.rb +6 -0
- data/lib/arel/select_manager.rb +237 -0
- data/lib/arel/sql/engine.rb +10 -0
- data/lib/arel/sql_literal.rb +4 -0
- data/lib/arel/table.rb +134 -0
- data/lib/arel/tree_manager.rb +36 -0
- data/lib/arel/update_manager.rb +49 -0
- data/lib/arel/visitors.rb +38 -0
- data/lib/arel/visitors/depth_first.rb +154 -0
- data/lib/arel/visitors/dot.rb +230 -0
- data/lib/arel/visitors/join_sql.rb +40 -0
- data/lib/arel/visitors/mssql.rb +16 -0
- data/lib/arel/visitors/mysql.rb +34 -0
- data/lib/arel/visitors/oracle.rb +116 -0
- data/lib/arel/visitors/order_clauses.rb +11 -0
- data/lib/arel/visitors/postgresql.rb +58 -0
- data/lib/arel/visitors/sqlite.rb +11 -0
- data/lib/arel/visitors/to_sql.rb +331 -0
- data/lib/arel/visitors/visitor.rb +27 -0
- data/lib/arel/visitors/where_sql.rb +9 -0
- data/square-arel.gemspec +36 -0
- data/test/attributes/test_attribute.rb +664 -0
- data/test/helper.rb +13 -0
- data/test/nodes/test_as.rb +16 -0
- data/test/nodes/test_count.rb +18 -0
- data/test/nodes/test_delete_statement.rb +14 -0
- data/test/nodes/test_equality.rb +74 -0
- data/test/nodes/test_insert_statement.rb +18 -0
- data/test/nodes/test_node.rb +33 -0
- data/test/nodes/test_not.rb +20 -0
- data/test/nodes/test_or.rb +22 -0
- data/test/nodes/test_select_core.rb +22 -0
- data/test/nodes/test_select_statement.rb +13 -0
- data/test/nodes/test_sql_literal.rb +52 -0
- data/test/nodes/test_sum.rb +12 -0
- data/test/nodes/test_update_statement.rb +18 -0
- data/test/support/fake_record.rb +91 -0
- data/test/test_activerecord_compat.rb +18 -0
- data/test/test_attributes.rb +46 -0
- data/test/test_crud.rb +69 -0
- data/test/test_delete_manager.rb +42 -0
- data/test/test_insert_manager.rb +125 -0
- data/test/test_select_manager.rb +659 -0
- data/test/test_table.rb +193 -0
- data/test/test_update_manager.rb +86 -0
- data/test/visitors/test_depth_first.rb +212 -0
- data/test/visitors/test_dot.rb +29 -0
- data/test/visitors/test_join_sql.rb +35 -0
- data/test/visitors/test_mssql.rb +18 -0
- data/test/visitors/test_mysql.rb +45 -0
- data/test/visitors/test_oracle.rb +147 -0
- data/test/visitors/test_postgres.rb +36 -0
- data/test/visitors/test_sqlite.rb +18 -0
- data/test/visitors/test_to_sql.rb +255 -0
- metadata +261 -0
@@ -0,0 +1,237 @@
|
|
1
|
+
module Arel
|
2
|
+
class SelectManager < Arel::TreeManager
|
3
|
+
include Arel::Crud
|
4
|
+
|
5
|
+
def initialize engine, table = nil
|
6
|
+
super(engine)
|
7
|
+
@ast = Nodes::SelectStatement.new
|
8
|
+
@ctx = @ast.cores.last
|
9
|
+
from table
|
10
|
+
end
|
11
|
+
|
12
|
+
def taken
|
13
|
+
@ast.limit && @ast.limit.expr
|
14
|
+
end
|
15
|
+
|
16
|
+
def constraints
|
17
|
+
@ctx.wheres
|
18
|
+
end
|
19
|
+
|
20
|
+
def skip amount
|
21
|
+
@ast.offset = Nodes::Offset.new(amount)
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
###
|
26
|
+
# Produces an Arel::Nodes::Exists node
|
27
|
+
def exists
|
28
|
+
Arel::Nodes::Exists.new @ast
|
29
|
+
end
|
30
|
+
|
31
|
+
def where_clauses
|
32
|
+
#warn "where_clauses is deprecated" if $VERBOSE
|
33
|
+
to_sql = Visitors::ToSql.new @engine
|
34
|
+
@ctx.wheres.map { |c| to_sql.accept c }
|
35
|
+
end
|
36
|
+
|
37
|
+
def lock locking = Arel.sql('FOR UPDATE')
|
38
|
+
case locking
|
39
|
+
when true
|
40
|
+
locking = Arel.sql('FOR UPDATE')
|
41
|
+
when Arel::Nodes::SqlLiteral
|
42
|
+
when String
|
43
|
+
locking = Arel.sql locking
|
44
|
+
end
|
45
|
+
|
46
|
+
@ast.lock = Nodes::Lock.new(locking)
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
def locked
|
51
|
+
@ast.lock
|
52
|
+
end
|
53
|
+
|
54
|
+
def on *exprs
|
55
|
+
@ctx.froms.constraint = Nodes::On.new(collapse(exprs))
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
def group *columns
|
60
|
+
columns.each do |column|
|
61
|
+
# FIXME: backwards compat
|
62
|
+
column = Nodes::SqlLiteral.new(column) if String === column
|
63
|
+
column = Nodes::SqlLiteral.new(column.to_s) if Symbol === column
|
64
|
+
|
65
|
+
@ctx.groups.push Nodes::Group.new column
|
66
|
+
end
|
67
|
+
self
|
68
|
+
end
|
69
|
+
|
70
|
+
def from table
|
71
|
+
table = Nodes::SqlLiteral.new(table) if String === table
|
72
|
+
# FIXME: this is a hack to support
|
73
|
+
# test_with_two_tables_in_from_without_getting_double_quoted
|
74
|
+
# from the AR tests.
|
75
|
+
if @ctx.froms
|
76
|
+
source = @ctx.froms
|
77
|
+
|
78
|
+
if Nodes::SqlLiteral === table && Nodes::Join === source
|
79
|
+
source.left = table
|
80
|
+
table = source
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
@ctx.froms = table
|
85
|
+
self
|
86
|
+
end
|
87
|
+
|
88
|
+
def join relation, klass = Nodes::InnerJoin
|
89
|
+
return self unless relation
|
90
|
+
|
91
|
+
case relation
|
92
|
+
when String, Nodes::SqlLiteral
|
93
|
+
raise if relation.blank?
|
94
|
+
from Nodes::StringJoin.new(@ctx.froms, relation)
|
95
|
+
else
|
96
|
+
from klass.new(@ctx.froms, relation, nil)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def having expr
|
101
|
+
expr = Nodes::SqlLiteral.new(expr) if String === expr
|
102
|
+
|
103
|
+
@ctx.having = Nodes::Having.new(expr)
|
104
|
+
self
|
105
|
+
end
|
106
|
+
|
107
|
+
def project *projections
|
108
|
+
# FIXME: converting these to SQLLiterals is probably not good, but
|
109
|
+
# rails tests require it.
|
110
|
+
@ctx.projections.concat projections.map { |x|
|
111
|
+
[Symbol, String].include?(x.class) ? SqlLiteral.new(x.to_s) : x
|
112
|
+
}
|
113
|
+
self
|
114
|
+
end
|
115
|
+
|
116
|
+
def order *expr
|
117
|
+
# FIXME: We SHOULD NOT be converting these to SqlLiteral automatically
|
118
|
+
@ast.orders.concat expr.map { |x|
|
119
|
+
String === x || Symbol === x ? Nodes::SqlLiteral.new(x.to_s) : x
|
120
|
+
}
|
121
|
+
self
|
122
|
+
end
|
123
|
+
|
124
|
+
def orders
|
125
|
+
@ast.orders
|
126
|
+
end
|
127
|
+
|
128
|
+
def wheres
|
129
|
+
Compatibility::Wheres.new @engine, @ctx.wheres
|
130
|
+
end
|
131
|
+
|
132
|
+
def where_sql
|
133
|
+
return if @ctx.wheres.empty?
|
134
|
+
|
135
|
+
viz = Visitors::WhereSql.new @engine
|
136
|
+
Nodes::SqlLiteral.new viz.accept @ctx
|
137
|
+
end
|
138
|
+
|
139
|
+
def union operation, other = nil
|
140
|
+
if other
|
141
|
+
node_class = Nodes.const_get("Union#{operation.to_s.capitalize}")
|
142
|
+
else
|
143
|
+
other = operation
|
144
|
+
node_class = Nodes::Union
|
145
|
+
end
|
146
|
+
|
147
|
+
node_class.new self.ast, other.ast
|
148
|
+
end
|
149
|
+
|
150
|
+
def intersect other
|
151
|
+
Nodes::Intersect.new ast, other.ast
|
152
|
+
end
|
153
|
+
|
154
|
+
def except other
|
155
|
+
Nodes::Except.new ast, other.ast
|
156
|
+
end
|
157
|
+
alias :minus :except
|
158
|
+
|
159
|
+
def take limit
|
160
|
+
@ast.limit = Nodes::Limit.new(limit)
|
161
|
+
@ctx.top = Nodes::Top.new(limit)
|
162
|
+
self
|
163
|
+
end
|
164
|
+
|
165
|
+
def join_sql
|
166
|
+
return nil unless @ctx.froms
|
167
|
+
|
168
|
+
viz = Visitors::JoinSql.new @engine
|
169
|
+
Nodes::SqlLiteral.new viz.accept @ctx
|
170
|
+
end
|
171
|
+
|
172
|
+
def order_clauses
|
173
|
+
Visitors::OrderClauses.new(@engine).accept(@ast).map { |x|
|
174
|
+
Nodes::SqlLiteral.new x
|
175
|
+
}
|
176
|
+
end
|
177
|
+
|
178
|
+
def joins manager
|
179
|
+
if $VERBOSE
|
180
|
+
warn "joins is deprecated and will be removed in 2.2"
|
181
|
+
warn "please remove your call to joins from #{caller.first}"
|
182
|
+
end
|
183
|
+
manager.join_sql
|
184
|
+
end
|
185
|
+
|
186
|
+
class Row < Struct.new(:data) # :nodoc:
|
187
|
+
def id
|
188
|
+
data['id']
|
189
|
+
end
|
190
|
+
|
191
|
+
def method_missing(name, *args)
|
192
|
+
name = name.to_s
|
193
|
+
return data[name] if data.key?(name)
|
194
|
+
super
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def to_a # :nodoc:
|
199
|
+
warn "to_a is deprecated. Please remove it from #{caller[0]}"
|
200
|
+
# FIXME: I think `select` should be made public...
|
201
|
+
@engine.connection.send(:select, to_sql, 'AREL').map { |x| Row.new(x) }
|
202
|
+
end
|
203
|
+
|
204
|
+
# FIXME: this method should go away
|
205
|
+
def insert values
|
206
|
+
im = InsertManager.new @engine
|
207
|
+
table = @ctx.froms
|
208
|
+
primary_key_name = (primary_key = table.primary_key) && primary_key.name
|
209
|
+
# FIXME: in AR tests values sometimes were Array and not Hash therefore is_a?(Hash) check is added
|
210
|
+
primary_key_value = primary_key && values.is_a?(Hash) && values[primary_key]
|
211
|
+
im.into table
|
212
|
+
im.insert values
|
213
|
+
# Oracle adapter needs primary key name to generate RETURNING ... INTO ... clause
|
214
|
+
# for tables which assign primary key value using trigger.
|
215
|
+
# RETURNING ... INTO ... clause will be added only if primary_key_value is nil
|
216
|
+
# therefore it is necessary to pass primary key value as well
|
217
|
+
@engine.connection.insert im.to_sql, 'AREL', primary_key_name, primary_key_value
|
218
|
+
end
|
219
|
+
|
220
|
+
private
|
221
|
+
def collapse exprs
|
222
|
+
exprs.map! { |x| x.class == ::String ? Arel.sql(x) : x }
|
223
|
+
|
224
|
+
return exprs.first if exprs.length == 1
|
225
|
+
|
226
|
+
right = exprs.pop
|
227
|
+
left = exprs.pop
|
228
|
+
|
229
|
+
right = Nodes::SqlLiteral.new(right) if String === right
|
230
|
+
|
231
|
+
right = Nodes::And.new left, right
|
232
|
+
exprs.reverse.inject(right) { |memo,expr|
|
233
|
+
Nodes::And.new(expr, memo)
|
234
|
+
}
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
data/lib/arel/table.rb
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
module Arel
|
2
|
+
class Table
|
3
|
+
include Arel::Crud
|
4
|
+
|
5
|
+
@engine = nil
|
6
|
+
class << self; attr_accessor :engine; end
|
7
|
+
|
8
|
+
attr_accessor :name, :engine, :aliases, :table_alias
|
9
|
+
|
10
|
+
def initialize name, engine = Table.engine
|
11
|
+
@name = name.to_s
|
12
|
+
@engine = engine
|
13
|
+
@columns = nil
|
14
|
+
@aliases = []
|
15
|
+
@table_alias = nil
|
16
|
+
@primary_key = nil
|
17
|
+
|
18
|
+
if Hash === engine
|
19
|
+
@engine = engine[:engine] || Table.engine
|
20
|
+
@columns = attributes_for engine[:columns]
|
21
|
+
|
22
|
+
# Sometime AR sends an :as parameter to table, to let the table know
|
23
|
+
# that it is an Alias. We may want to override new, and return a
|
24
|
+
# TableAlias node?
|
25
|
+
@table_alias = engine[:as] unless engine[:as].to_s == @name
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def primary_key
|
30
|
+
@primary_key ||= begin
|
31
|
+
primary_key_name = @engine.connection.primary_key(name)
|
32
|
+
# some tables might be without primary key
|
33
|
+
primary_key_name && self[primary_key_name]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def alias name = "#{self.name}_2"
|
38
|
+
Nodes::TableAlias.new(name, self).tap do |node|
|
39
|
+
@aliases << node
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def from table
|
44
|
+
SelectManager.new(@engine, table)
|
45
|
+
end
|
46
|
+
|
47
|
+
def joins manager
|
48
|
+
if $VERBOSE
|
49
|
+
warn "joins is deprecated and will be removed in 2.2"
|
50
|
+
warn "please remove your call to joins from #{caller.first}"
|
51
|
+
end
|
52
|
+
nil
|
53
|
+
end
|
54
|
+
|
55
|
+
def join relation, klass = Nodes::InnerJoin
|
56
|
+
return from(self) unless relation
|
57
|
+
|
58
|
+
case relation
|
59
|
+
when String, Nodes::SqlLiteral
|
60
|
+
raise if relation.blank?
|
61
|
+
from Nodes::StringJoin.new(self, relation)
|
62
|
+
else
|
63
|
+
from klass.new(self, relation, nil)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def group *columns
|
68
|
+
from(self).group(*columns)
|
69
|
+
end
|
70
|
+
|
71
|
+
def order *expr
|
72
|
+
from(self).order(*expr)
|
73
|
+
end
|
74
|
+
|
75
|
+
def where condition
|
76
|
+
from(self).where condition
|
77
|
+
end
|
78
|
+
|
79
|
+
def project *things
|
80
|
+
from(self).project(*things)
|
81
|
+
end
|
82
|
+
|
83
|
+
def take amount
|
84
|
+
from(self).take amount
|
85
|
+
end
|
86
|
+
|
87
|
+
def skip amount
|
88
|
+
from(self).skip amount
|
89
|
+
end
|
90
|
+
|
91
|
+
def having expr
|
92
|
+
from(self).having expr
|
93
|
+
end
|
94
|
+
|
95
|
+
def columns
|
96
|
+
@columns ||=
|
97
|
+
attributes_for @engine.connection.columns(@name, "#{@name} Columns")
|
98
|
+
end
|
99
|
+
|
100
|
+
def [] name
|
101
|
+
return nil unless table_exists?
|
102
|
+
|
103
|
+
name = name.to_sym
|
104
|
+
columns.find { |column| column.name == name }
|
105
|
+
end
|
106
|
+
|
107
|
+
def select_manager
|
108
|
+
SelectManager.new(@engine)
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
def attributes_for columns
|
114
|
+
return nil unless columns
|
115
|
+
|
116
|
+
columns.map do |column|
|
117
|
+
Attributes.for(column).new self, column.name.to_sym, column
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def table_exists?
|
122
|
+
@table_exists ||= tables.key?(@name) || engine.connection.table_exists?(name)
|
123
|
+
end
|
124
|
+
|
125
|
+
def tables
|
126
|
+
self.class.table_cache(@engine)
|
127
|
+
end
|
128
|
+
|
129
|
+
@@table_cache = nil
|
130
|
+
def self.table_cache engine # :nodoc:
|
131
|
+
@@table_cache ||= Hash[engine.connection.tables.map { |x| [x,true] }]
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Arel
|
2
|
+
class TreeManager
|
3
|
+
# FIXME: Remove this.
|
4
|
+
include Arel::Relation
|
5
|
+
|
6
|
+
attr_accessor :visitor
|
7
|
+
attr_reader :ast, :engine
|
8
|
+
|
9
|
+
def initialize engine
|
10
|
+
@engine = engine
|
11
|
+
@visitor = Visitors.visitor_for @engine
|
12
|
+
@ctx = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_dot
|
16
|
+
Visitors::Dot.new.accept @ast
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_sql
|
20
|
+
@visitor.accept @ast
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize_copy other
|
24
|
+
super
|
25
|
+
@ast = @ast.clone
|
26
|
+
end
|
27
|
+
|
28
|
+
def where expr
|
29
|
+
if Arel::TreeManager === expr
|
30
|
+
expr = expr.ast
|
31
|
+
end
|
32
|
+
@ctx.wheres << expr
|
33
|
+
self
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Arel
|
2
|
+
class UpdateManager < Arel::TreeManager
|
3
|
+
def initialize engine
|
4
|
+
super
|
5
|
+
@ast = Nodes::UpdateStatement.new
|
6
|
+
@ctx = @ast
|
7
|
+
end
|
8
|
+
|
9
|
+
def take limit
|
10
|
+
@ast.limit = Nodes::Limit.new(limit) if limit
|
11
|
+
self
|
12
|
+
end
|
13
|
+
|
14
|
+
def order *expr
|
15
|
+
@ast.orders = expr
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
###
|
20
|
+
# UPDATE +table+
|
21
|
+
def table table
|
22
|
+
@ast.relation = table
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
def wheres= exprs
|
27
|
+
@ast.wheres = exprs
|
28
|
+
end
|
29
|
+
|
30
|
+
def where expr
|
31
|
+
@ast.wheres << expr
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
def set values
|
36
|
+
if String === values
|
37
|
+
@ast.values = [values]
|
38
|
+
else
|
39
|
+
@ast.values = values.map { |column,value|
|
40
|
+
Nodes::Assignment.new(
|
41
|
+
Nodes::UnqualifiedColumn.new(column),
|
42
|
+
value
|
43
|
+
)
|
44
|
+
}
|
45
|
+
end
|
46
|
+
self
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|