mondrian-olap 0.4.0-java
Sign up to get free protection for your applications and to get access to all the features.
- data/.rspec +2 -0
- data/Changelog.md +60 -0
- data/Gemfile +21 -0
- data/LICENSE-Mondrian.html +259 -0
- data/LICENSE.txt +22 -0
- data/README.md +302 -0
- data/RUNNING_TESTS.rdoc +66 -0
- data/Rakefile +48 -0
- data/VERSION +1 -0
- data/lib/mondrian-olap.rb +1 -0
- data/lib/mondrian/jars/commons-collections-3.1.jar +0 -0
- data/lib/mondrian/jars/commons-dbcp-1.2.1.jar +0 -0
- data/lib/mondrian/jars/commons-logging-1.0.4.jar +0 -0
- data/lib/mondrian/jars/commons-math-1.0.jar +0 -0
- data/lib/mondrian/jars/commons-pool-1.2.jar +0 -0
- data/lib/mondrian/jars/commons-vfs-1.0.jar +0 -0
- data/lib/mondrian/jars/eigenbase-properties.jar +0 -0
- data/lib/mondrian/jars/eigenbase-resgen.jar +0 -0
- data/lib/mondrian/jars/eigenbase-xom.jar +0 -0
- data/lib/mondrian/jars/javacup.jar +0 -0
- data/lib/mondrian/jars/log4j-1.2.8.jar +0 -0
- data/lib/mondrian/jars/log4j.properties +5 -0
- data/lib/mondrian/jars/mondrian.jar +0 -0
- data/lib/mondrian/jars/olap4j.jar +0 -0
- data/lib/mondrian/olap.rb +17 -0
- data/lib/mondrian/olap/connection.rb +201 -0
- data/lib/mondrian/olap/cube.rb +297 -0
- data/lib/mondrian/olap/error.rb +57 -0
- data/lib/mondrian/olap/query.rb +342 -0
- data/lib/mondrian/olap/result.rb +264 -0
- data/lib/mondrian/olap/schema.rb +378 -0
- data/lib/mondrian/olap/schema_element.rb +153 -0
- data/lib/mondrian/olap/schema_udf.rb +282 -0
- data/mondrian-olap.gemspec +128 -0
- data/spec/connection_role_spec.rb +130 -0
- data/spec/connection_spec.rb +72 -0
- data/spec/cube_spec.rb +318 -0
- data/spec/fixtures/MondrianTest.xml +134 -0
- data/spec/fixtures/MondrianTestOracle.xml +134 -0
- data/spec/mondrian_spec.rb +53 -0
- data/spec/query_spec.rb +807 -0
- data/spec/rake_tasks.rb +260 -0
- data/spec/schema_definition_spec.rb +1249 -0
- data/spec/spec_helper.rb +134 -0
- data/spec/support/matchers/be_like.rb +24 -0
- metadata +278 -0
@@ -0,0 +1,378 @@
|
|
1
|
+
require 'mondrian/olap/schema_element'
|
2
|
+
|
3
|
+
module Mondrian
|
4
|
+
module OLAP
|
5
|
+
# See http://mondrian.pentaho.com/documentation/schema.php for more detailed description
|
6
|
+
# of Mondrian Schema elements.
|
7
|
+
class Schema < SchemaElement
|
8
|
+
def initialize(name = nil, attributes = {}, &block)
|
9
|
+
name, attributes = self.class.pre_process_arguments(name, attributes)
|
10
|
+
pre_process_attributes(attributes)
|
11
|
+
super(name, attributes, &block)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.define(name = nil, attributes = {}, &block)
|
15
|
+
name, attributes = pre_process_arguments(name, attributes)
|
16
|
+
new(name || 'default', attributes, &block)
|
17
|
+
end
|
18
|
+
|
19
|
+
def define(name = nil, attributes = {}, &block)
|
20
|
+
name, attributes = self.class.pre_process_arguments(name, attributes)
|
21
|
+
pre_process_attributes(attributes)
|
22
|
+
@attributes[:name] = name || @attributes[:name] || 'default' # otherwise connection with empty name fails
|
23
|
+
instance_eval(&block) if block
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def include_schema(shared_schema)
|
28
|
+
shared_schema.class.elements.each do |element|
|
29
|
+
instance_variable_get("@#{pluralize(element)}").concat shared_schema.send(pluralize(element))
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def self.pre_process_arguments(name, attributes)
|
36
|
+
# if is called just with attributes hash and without name
|
37
|
+
if name.is_a?(Hash) && attributes.empty?
|
38
|
+
attributes = name
|
39
|
+
name = nil
|
40
|
+
end
|
41
|
+
[name, attributes]
|
42
|
+
end
|
43
|
+
|
44
|
+
def pre_process_attributes(attributes)
|
45
|
+
unless attributes[:upcase_data_dictionary].nil?
|
46
|
+
@upcase_data_dictionary = attributes.delete(:upcase_data_dictionary)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
public
|
51
|
+
|
52
|
+
attributes :name, :description
|
53
|
+
elements :cube, :role, :user_defined_function
|
54
|
+
|
55
|
+
class Cube < SchemaElement
|
56
|
+
attributes :name, :description,
|
57
|
+
# The name of the measure that would be taken as the default measure of the cube.
|
58
|
+
:default_measure,
|
59
|
+
# Should the Fact table data for this Cube be cached by Mondrian or not.
|
60
|
+
# The default action is to cache the data.
|
61
|
+
:cache,
|
62
|
+
# Whether element is enabled - if true, then the Cube is realized otherwise it is ignored.
|
63
|
+
:enabled
|
64
|
+
elements :table, :view, :dimension, :measure, :calculated_member
|
65
|
+
end
|
66
|
+
|
67
|
+
class Table < SchemaElement
|
68
|
+
attributes :name, :schema, # Optional qualifier for table.
|
69
|
+
# Alias to be used with this table when it is used to form queries.
|
70
|
+
# If not specified, defaults to the table name, but in any case, must be unique within the schema.
|
71
|
+
# (You can use the same table in different hierarchies, but it must have different aliases.)
|
72
|
+
:alias
|
73
|
+
data_dictionary_names :name, :schema, :alias # values in XML will be uppercased when using Oracle driver
|
74
|
+
elements :agg_exclude, :agg_name, :agg_pattern, :sql
|
75
|
+
end
|
76
|
+
|
77
|
+
class View < SchemaElement
|
78
|
+
attributes :alias
|
79
|
+
data_dictionary_names :alias
|
80
|
+
# Defines a "table" using SQL query which can have different variants for different underlying databases
|
81
|
+
elements :sql
|
82
|
+
end
|
83
|
+
|
84
|
+
class Dimension < SchemaElement
|
85
|
+
attributes :name, :description,
|
86
|
+
# The dimension's type may be one of "Standard" or "Time".
|
87
|
+
# A time dimension will allow the use of the MDX time functions (WTD, YTD, QTD, etc.).
|
88
|
+
# Use a standard dimension if the dimension is not a time-related dimension.
|
89
|
+
# The default value is "Standard".
|
90
|
+
:type,
|
91
|
+
# The name of the column in the fact table which joins to the leaf level of this dimension.
|
92
|
+
# Required in a private Dimension or a DimensionUsage, but not in a public Dimension.
|
93
|
+
:foreign_key
|
94
|
+
data_dictionary_names :foreign_key # values in XML will be uppercased when using Oracle driver
|
95
|
+
elements :hierarchy
|
96
|
+
end
|
97
|
+
|
98
|
+
class Hierarchy < SchemaElement
|
99
|
+
attributes :name, :description,
|
100
|
+
# Whether this hierarchy has an 'all' member.
|
101
|
+
:has_all,
|
102
|
+
# Name of the 'all' member. If this attribute is not specified,
|
103
|
+
# the all member is named 'All hierarchyName', for example, 'All Store'.
|
104
|
+
:all_member_name,
|
105
|
+
# Name of the 'all' level. If this attribute is not specified,
|
106
|
+
# the all member is named '(All)'.
|
107
|
+
:all_level_name,
|
108
|
+
# The name of the column which identifies members, and which is referenced by rows in the fact table.
|
109
|
+
# If not specified, the key of the lowest level is used. See also Dimension foreign_key.
|
110
|
+
:primary_key,
|
111
|
+
# The name of the table which contains primary_key.
|
112
|
+
# If the hierarchy has only one table, defaults to that; it is required.
|
113
|
+
:primary_key_table,
|
114
|
+
# Should be set to the level (if such a level exists) at which depth it is known
|
115
|
+
# that all members have entirely unique rows, allowing SQL GROUP BY clauses to be completely eliminated from the query.
|
116
|
+
:unique_key_level_name
|
117
|
+
data_dictionary_names :primary_key, :primary_key_table # values in XML will be uppercased when using Oracle driver
|
118
|
+
elements :table, :join, :property, :level
|
119
|
+
end
|
120
|
+
|
121
|
+
class Join < SchemaElement
|
122
|
+
attributes :left_key, :right_key, :left_alias, :right_alias
|
123
|
+
data_dictionary_names :left_key, :right_key, :left_alias, :right_alias # values in XML will be uppercased when using Oracle driver
|
124
|
+
elements :table, :join
|
125
|
+
end
|
126
|
+
|
127
|
+
class Level < SchemaElement
|
128
|
+
attributes :name, :description,
|
129
|
+
# The name of the table that the column comes from.
|
130
|
+
# If this hierarchy is based upon just one table, defaults to the name of that table;
|
131
|
+
# otherwise, it is required.
|
132
|
+
:table,
|
133
|
+
# The name of the column which holds the unique identifier of this level.
|
134
|
+
:column,
|
135
|
+
# The name of the column which holds the user identifier of this level.
|
136
|
+
:name_column,
|
137
|
+
# The name of the column which holds member ordinals.
|
138
|
+
# If this column is not specified, the key column is used for ordering.
|
139
|
+
:ordinal_column,
|
140
|
+
# The name of the column which references the parent member in a parent-child hierarchy.
|
141
|
+
:parent_column,
|
142
|
+
# Value which identifies null parents in a parent-child hierarchy.
|
143
|
+
# Typical values are 'NULL' and '0'.
|
144
|
+
:null_parent_value,
|
145
|
+
# Indicates the type of this level's key column:
|
146
|
+
# String, Numeric, Integer, Boolean, Date, Time or Timestamp.
|
147
|
+
# When generating SQL statements, Mondrian encloses values for String columns in quotation marks,
|
148
|
+
# but leaves values for Integer and Numeric columns un-quoted.
|
149
|
+
# Date, Time, and Timestamp values are quoted according to the SQL dialect.
|
150
|
+
# For a SQL-compliant dialect, the values appear prefixed by their typename,
|
151
|
+
# for example, "DATE '2006-06-01'".
|
152
|
+
# Default value: 'String'
|
153
|
+
:type,
|
154
|
+
# Whether members are unique across all parents.
|
155
|
+
# For example, zipcodes are unique across all states.
|
156
|
+
# The first level's members are always unique.
|
157
|
+
# Default value: false
|
158
|
+
:unique_members,
|
159
|
+
# Whether this is a regular or a time-related level.
|
160
|
+
# The value makes a difference to time-related functions such as YTD (year-to-date).
|
161
|
+
# Default value: 'Regular'
|
162
|
+
:level_type,
|
163
|
+
# Condition which determines whether a member of this level is hidden.
|
164
|
+
# If a hierarchy has one or more levels with hidden members,
|
165
|
+
# then it is possible that not all leaf members are the same distance from the root,
|
166
|
+
# and it is termed a ragged hierarchy.
|
167
|
+
# Allowable values are: Never (a member always appears; the default);
|
168
|
+
# IfBlankName (a member doesn't appear if its name is null, empty or all whitespace);
|
169
|
+
# and IfParentsName (a member appears unless its name matches the parent's.
|
170
|
+
# Default value: 'Never'
|
171
|
+
:hide_member_if,
|
172
|
+
# The estimated number of members in this level. Setting this property improves the performance of
|
173
|
+
# MDSCHEMA_LEVELS, MDSCHEMA_HIERARCHIES and MDSCHEMA_DIMENSIONS XMLA requests
|
174
|
+
:approx_row_count
|
175
|
+
data_dictionary_names :table, :column, :name_column, :ordinal_column, :parent_column # values in XML will be uppercased when using Oracle driver
|
176
|
+
elements :key_expression, :name_expression, :ordinal_expression, :member_formatter, :property
|
177
|
+
end
|
178
|
+
|
179
|
+
class KeyExpression < SchemaElement
|
180
|
+
elements :sql
|
181
|
+
end
|
182
|
+
|
183
|
+
class NameExpression < SchemaElement
|
184
|
+
elements :sql
|
185
|
+
end
|
186
|
+
|
187
|
+
class OrdinalExpression < SchemaElement
|
188
|
+
elements :sql
|
189
|
+
end
|
190
|
+
|
191
|
+
class Sql < SchemaElement
|
192
|
+
def self.name
|
193
|
+
'SQL'
|
194
|
+
end
|
195
|
+
attributes :dialect
|
196
|
+
content :text
|
197
|
+
end
|
198
|
+
|
199
|
+
class Property < SchemaElement
|
200
|
+
attributes :name, :description,
|
201
|
+
:column,
|
202
|
+
# Data type of this property: String, Numeric, Integer, Boolean, Date, Time or Timestamp.
|
203
|
+
:type,
|
204
|
+
# Should be set to true if the value of the property is functionally dependent on the level value.
|
205
|
+
# This permits the associated property column to be omitted from the GROUP BY clause
|
206
|
+
# (if the database permits columns in the SELECT that are not in the GROUP BY).
|
207
|
+
# This can be a significant performance enhancement on some databases, such as MySQL.
|
208
|
+
:depends_on_level_value
|
209
|
+
data_dictionary_names :column
|
210
|
+
elements :property_formatter
|
211
|
+
end
|
212
|
+
|
213
|
+
class Measure < SchemaElement
|
214
|
+
attributes :name, :description,
|
215
|
+
# Column which is source of this measure's values.
|
216
|
+
# If not specified, a measure expression must be specified.
|
217
|
+
:column,
|
218
|
+
# The datatype of this measure: String, Numeric, Integer, Boolean, Date, Time or Timestamp.
|
219
|
+
# The default datatype of a measure is 'Integer' if the measure's aggregator is 'Count', otherwise it is 'Numeric'.
|
220
|
+
:datatype,
|
221
|
+
# Aggregation function. Allowed values are "sum", "count", "min", "max", "avg", and "distinct-count".
|
222
|
+
:aggregator,
|
223
|
+
# Format string with which to format cells of this measure. For more details, see the mondrian.util.Format class.
|
224
|
+
:format_string,
|
225
|
+
# Whether this member is visible in the user-interface. Default true.
|
226
|
+
:visible
|
227
|
+
data_dictionary_names :column # values in XML will be uppercased when using Oracle driver
|
228
|
+
elements :measure_expression, :cell_formatter
|
229
|
+
end
|
230
|
+
|
231
|
+
class MeasureExpression < SchemaElement
|
232
|
+
elements :sql
|
233
|
+
end
|
234
|
+
|
235
|
+
class CalculatedMember < SchemaElement
|
236
|
+
attributes :name, :description,
|
237
|
+
# Name of the dimension which this member belongs to.
|
238
|
+
:dimension,
|
239
|
+
# Format string with which to format cells of this measure. For more details, see the mondrian.util.Format class.
|
240
|
+
:format_string,
|
241
|
+
# Whether this member is visible in the user-interface. Default true.
|
242
|
+
:visible
|
243
|
+
elements :formula, :calculated_member_property, :cell_formatter
|
244
|
+
end
|
245
|
+
|
246
|
+
class Formula < SchemaElement
|
247
|
+
content :text
|
248
|
+
end
|
249
|
+
|
250
|
+
class CalculatedMemberProperty < SchemaElement
|
251
|
+
attributes :name, :description,
|
252
|
+
# MDX expression which defines the value of this property. If the expression is a constant string, you could enclose it in quotes,
|
253
|
+
# or just specify the 'value' attribute instead.
|
254
|
+
:expression,
|
255
|
+
# Value of this property. If the value is not constant, specify the 'expression' attribute instead.
|
256
|
+
:value
|
257
|
+
end
|
258
|
+
|
259
|
+
class AggName < SchemaElement
|
260
|
+
attributes :name
|
261
|
+
data_dictionary_names :name
|
262
|
+
elements :agg_fact_count, :agg_measure, :agg_level, :agg_foreign_key
|
263
|
+
end
|
264
|
+
|
265
|
+
class AggFactCount < SchemaElement
|
266
|
+
attributes :column
|
267
|
+
data_dictionary_names :column
|
268
|
+
end
|
269
|
+
|
270
|
+
class AggMeasure < SchemaElement
|
271
|
+
attributes :name, :column
|
272
|
+
data_dictionary_names :column
|
273
|
+
end
|
274
|
+
|
275
|
+
class AggLevel < SchemaElement
|
276
|
+
attributes :name, :column
|
277
|
+
data_dictionary_names :column
|
278
|
+
end
|
279
|
+
|
280
|
+
class AggForeignKey < SchemaElement
|
281
|
+
attributes :fact_column, :agg_column
|
282
|
+
data_dictionary_names :fact_column, :agg_column
|
283
|
+
end
|
284
|
+
|
285
|
+
class AggIgnoreColumn < SchemaElement
|
286
|
+
attributes :column
|
287
|
+
data_dictionary_names :column
|
288
|
+
end
|
289
|
+
|
290
|
+
class AggPattern < SchemaElement
|
291
|
+
attributes :pattern
|
292
|
+
data_dictionary_names :pattern
|
293
|
+
elements :agg_fact_count, :agg_measure, :agg_level, :agg_foreign_key, :agg_exclude
|
294
|
+
end
|
295
|
+
|
296
|
+
class AggExclude < SchemaElement
|
297
|
+
attributes :name, :pattern, :ignorecase
|
298
|
+
data_dictionary_names :name, :pattern
|
299
|
+
end
|
300
|
+
|
301
|
+
class Role < SchemaElement
|
302
|
+
attributes :name
|
303
|
+
elements :schema_grant, :union
|
304
|
+
end
|
305
|
+
|
306
|
+
class SchemaGrant < SchemaElement
|
307
|
+
# access may be "all", "all_dimensions", "custom" or "none".
|
308
|
+
# If access is "all_dimensions", the role has access to all dimensions but still needs explicit access to cubes.
|
309
|
+
# If access is "custom", no access will be inherited by cubes for which no explicit rule is set.
|
310
|
+
# If access is "all_dimensions", an implicut access is given to all dimensions of the schema's cubes,
|
311
|
+
# provided the cube's access attribute is either "custom" or "all"
|
312
|
+
attributes :access
|
313
|
+
elements :cube_grant
|
314
|
+
end
|
315
|
+
|
316
|
+
class CubeGrant < SchemaElement
|
317
|
+
# access may be "all", "custom", or "none".
|
318
|
+
# If access is "custom", no access will be inherited by the dimensions of this cube,
|
319
|
+
# unless the parent SchemaGrant is set to "all_dimensions"
|
320
|
+
attributes :access,
|
321
|
+
# The unique name of the cube
|
322
|
+
:cube
|
323
|
+
elements :dimension_grant, :hierarchy_grant
|
324
|
+
end
|
325
|
+
|
326
|
+
class DimensionGrant < SchemaElement
|
327
|
+
# access may be "all", "custom" or "none".
|
328
|
+
# Note that a role is implicitly given access to a dimension when it is given "all" acess to a cube.
|
329
|
+
# If access is "custom", no access will be inherited by the hierarchies of this dimension.
|
330
|
+
# If the parent schema access is "all_dimensions", this timension will inherit access "all".
|
331
|
+
# See also the "all_dimensions" option of the "SchemaGrant" element.
|
332
|
+
attributes :access,
|
333
|
+
# The unique name of the dimension
|
334
|
+
:dimension
|
335
|
+
end
|
336
|
+
|
337
|
+
class HierarchyGrant < SchemaElement
|
338
|
+
# access may be "all", "custom" or "none".
|
339
|
+
# If access is "custom", you may also specify the attributes :top_level, :bottom_level, and the member grants.
|
340
|
+
# If access is "custom", the child levels of this hierarchy will not inherit access rights from this hierarchy,
|
341
|
+
# should there be no explicit rules defined for the said child level.
|
342
|
+
attributes :access,
|
343
|
+
# The unique name of the hierarchy
|
344
|
+
:hierarchy,
|
345
|
+
# Unique name of the highest level of the hierarchy from which this role is allowed to see members.
|
346
|
+
# May only be specified if the HierarchyGrant.access is "custom".
|
347
|
+
# If not specified, role can see members up to the top level.
|
348
|
+
:top_level,
|
349
|
+
# Unique name of the lowest level of the hierarchy from which this role is allowed to see members.
|
350
|
+
# May only be specified if the HierarchyGrant.access is "custom".
|
351
|
+
# If not specified, role can see members down to the leaf level.
|
352
|
+
:bottom_level,
|
353
|
+
# Policy which determines how cell values are calculated if not all of the children of the current cell
|
354
|
+
# are visible to the current role.
|
355
|
+
# Allowable values are "full" (the default), "partial", and "hidden".
|
356
|
+
:rollup_policy
|
357
|
+
elements :member_grant
|
358
|
+
end
|
359
|
+
|
360
|
+
class MemberGrant < SchemaElement
|
361
|
+
# The children of this member inherit that access.
|
362
|
+
# You can implicitly see a member if you can see any of its children.
|
363
|
+
attributes :access,
|
364
|
+
# The unique name of the member
|
365
|
+
:member
|
366
|
+
end
|
367
|
+
|
368
|
+
class Union < SchemaElement
|
369
|
+
elements :role_usage
|
370
|
+
end
|
371
|
+
|
372
|
+
class RoleUsage < SchemaElement
|
373
|
+
attributes :role_name
|
374
|
+
end
|
375
|
+
|
376
|
+
end
|
377
|
+
end
|
378
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
module Mondrian
|
4
|
+
module OLAP
|
5
|
+
class SchemaElement
|
6
|
+
def initialize(name = nil, attributes = {}, &block)
|
7
|
+
# if just attributes hash provided
|
8
|
+
if name.is_a?(Hash) && attributes == {}
|
9
|
+
attributes = name
|
10
|
+
name = nil
|
11
|
+
end
|
12
|
+
@attributes = {}
|
13
|
+
if name
|
14
|
+
if self.class.content
|
15
|
+
@content = name
|
16
|
+
else
|
17
|
+
@attributes[:name] = name
|
18
|
+
end
|
19
|
+
end
|
20
|
+
@attributes.merge!(attributes)
|
21
|
+
self.class.elements.each do |element|
|
22
|
+
instance_variable_set("@#{pluralize(element)}", [])
|
23
|
+
end
|
24
|
+
@xml_fragments = []
|
25
|
+
instance_eval(&block) if block
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.attributes(*names)
|
29
|
+
names.each do |name|
|
30
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
31
|
+
def #{name}(*args)
|
32
|
+
if args.empty?
|
33
|
+
@attributes[:#{name}]
|
34
|
+
elsif args.size == 1
|
35
|
+
@attributes[:#{name}] = args[0]
|
36
|
+
else
|
37
|
+
raise ArgumentError, "too many arguments"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
RUBY
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.data_dictionary_names(*names)
|
45
|
+
return @data_dictionary_names || [] if names.empty?
|
46
|
+
@data_dictionary_names ||= []
|
47
|
+
@data_dictionary_names.concat(names)
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.elements(*names)
|
51
|
+
return @elements || [] if names.empty?
|
52
|
+
|
53
|
+
@elements ||= []
|
54
|
+
@elements.concat(names)
|
55
|
+
|
56
|
+
names.each do |name|
|
57
|
+
attr_reader pluralize(name).to_sym
|
58
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
59
|
+
def #{name}(name=nil, attributes = {}, &block)
|
60
|
+
@#{pluralize(name)} << Schema::#{camel_case(name)}.new(name, attributes, &block)
|
61
|
+
end
|
62
|
+
RUBY
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.content(type=nil)
|
67
|
+
return @content if type.nil?
|
68
|
+
@content = type
|
69
|
+
end
|
70
|
+
|
71
|
+
attr_reader :xml_fragments
|
72
|
+
def xml(string)
|
73
|
+
string = string.strip
|
74
|
+
fragment = Nokogiri::XML::DocumentFragment.parse(string)
|
75
|
+
raise ArgumentError, "Invalid XML fragment:\n#{string}" if fragment.children.empty?
|
76
|
+
@xml_fragments << string
|
77
|
+
end
|
78
|
+
|
79
|
+
def to_xml(options={})
|
80
|
+
options[:upcase_data_dictionary] = @upcase_data_dictionary unless @upcase_data_dictionary.nil?
|
81
|
+
Nokogiri::XML::Builder.new do |xml|
|
82
|
+
add_to_xml(xml, options)
|
83
|
+
end.to_xml
|
84
|
+
end
|
85
|
+
|
86
|
+
protected
|
87
|
+
|
88
|
+
def add_to_xml(xml, options)
|
89
|
+
if self.class.content
|
90
|
+
xml.send(tag_name(self.class.name), @content, xmlized_attributes(options))
|
91
|
+
else
|
92
|
+
xml.send(tag_name(self.class.name), xmlized_attributes(options)) do
|
93
|
+
self.class.elements.each do |element|
|
94
|
+
instance_variable_get("@#{pluralize(element)}").each {|item| item.add_to_xml(xml, options)}
|
95
|
+
end
|
96
|
+
@xml_fragments.each do |xml_fragment|
|
97
|
+
xml.send(:insert, Nokogiri::XML::DocumentFragment.parse(xml_fragment))
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def xmlized_attributes(options)
|
106
|
+
# data dictionary values should be in uppercase if schema defined with :upcase_data_dictionary => true
|
107
|
+
# or by default when using Oracle or LucidDB driver (can be overridden by :upcase_data_dictionary => false)
|
108
|
+
upcase_attributes = if options[:upcase_data_dictionary].nil? && %w(oracle luciddb).include?(options[:driver]) ||
|
109
|
+
options[:upcase_data_dictionary]
|
110
|
+
self.class.data_dictionary_names
|
111
|
+
else
|
112
|
+
[]
|
113
|
+
end
|
114
|
+
hash = {}
|
115
|
+
@attributes.each do |attr, value|
|
116
|
+
value = value.upcase if upcase_attributes.include?(attr)
|
117
|
+
hash[
|
118
|
+
# camelcase attribute name
|
119
|
+
attr.to_s.gsub(/_([^_]+)/){|m| $1.capitalize}
|
120
|
+
] = value
|
121
|
+
end
|
122
|
+
hash
|
123
|
+
end
|
124
|
+
|
125
|
+
def self.pluralize(string)
|
126
|
+
string = string.to_s
|
127
|
+
case string
|
128
|
+
when /^(.*)y$/
|
129
|
+
"#{$1}ies"
|
130
|
+
else
|
131
|
+
"#{string}s"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def pluralize(string)
|
136
|
+
self.class.pluralize(string)
|
137
|
+
end
|
138
|
+
|
139
|
+
def self.camel_case(string)
|
140
|
+
string.to_s.split('_').map{|s| s.capitalize}.join('')
|
141
|
+
end
|
142
|
+
|
143
|
+
def camel_case(string)
|
144
|
+
self.class.camel_case(string)
|
145
|
+
end
|
146
|
+
|
147
|
+
def tag_name(string)
|
148
|
+
string.split('::').last << '_'
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
153
|
+
end
|