rubiks 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -1,20 +1,35 @@
1
- *.gem
2
- *.rbc
1
+ ## TEXTMATE
2
+ *.tmproj
3
+ tmtags
4
+
5
+ ## EMACS
6
+ *~
7
+ \#*
8
+ .\#*
9
+
10
+ ## VIM
3
11
  *.swp
4
- .DS_Store
5
- .bundle
6
- .config
7
- .vagrant
12
+
13
+ ## PROJECT::GENERAL
8
14
  .yardoc
9
- Gemfile.lock
10
- InstalledFiles
11
- _yardoc
12
15
  coverage
13
- doc/
14
- lib/bundler/man
16
+ doc
17
+ log
15
18
  pkg
16
19
  rdoc
17
- spec/reports
18
- test/tmp
19
- test/version_tmp
20
- tmp
20
+
21
+ ## BUNDLER
22
+ *.gem
23
+ .bundle
24
+ pkg
25
+ Gemfile.lock
26
+
27
+ ## RBENV
28
+ .ruby-version
29
+ .rbenv*
30
+
31
+ ## RCOV
32
+ coverage.data
33
+
34
+ ## RUBINIUS
35
+ *.rbc
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use 1.9.3
data/.travis.yml CHANGED
@@ -1,6 +1,5 @@
1
- language: ruby
2
1
  bundler_args: --without development
2
+ language: ruby
3
3
  rvm:
4
- - '1.9.2'
5
- notifications:
6
- email: false
4
+ - jruby-19mode
5
+ - 1.9.2
data/.yardopts ADDED
@@ -0,0 +1,6 @@
1
+ --markup markdown
2
+ -
3
+ CHANGELOG.md
4
+ CONTRIBUTING.md
5
+ LICENSE.md
6
+ README.md
data/CHANGELOG.md ADDED
@@ -0,0 +1,21 @@
1
+ 0.0.5
2
+ -----
3
+ * [0.0.5 release](https://github.com/moneydesktop/rubiks/pull/2)
4
+
5
+ 0.0.4
6
+ -----
7
+ * [Bump to v0.0.4](https://github.com/moneydesktop/rubiks/commit/38dfd1f82e947f39457670adfdc6ca67da2728c6)
8
+ * [Add GemVersion badge to README](https://github.com/moneydesktop/rubiks/commit/2d92097c4ba73d92690fab78455fc7db6ed0b3d9)
9
+
10
+ 0.0.3
11
+ -----
12
+ * [Bump to v0.0.3](https://github.com/moneydesktop/rubiks/commit/23ceb29936f663605527a12ca2d2a5c623abe99e)
13
+ * Need to fill in other commits here
14
+
15
+ 0.0.2
16
+ -----
17
+ * [Bump to v0.0.2](https://github.com/moneydesktop/rubiks/commit/14a55449be9552268c66cbf8b902af02cf7b73c6)
18
+
19
+ 0.0.1
20
+ -----
21
+ * [Initial RubyGem project](https://github.com/moneydesktop/rubiks/commit/fa8be4badf70e86fda7e14bf23f9230a926893d2)
data/CONTRIBUTIG.md ADDED
@@ -0,0 +1,46 @@
1
+ ## Contributing
2
+ In the spirit of [free software][free-sw], **everyone** is encouraged to help
3
+ improve this project.
4
+
5
+ [free-sw]: http://www.fsf.org/licensing/essays/free-sw.html
6
+
7
+ Here are some ways *you* can contribute:
8
+
9
+ * by using alpha, beta, and prerelease versions
10
+ * by reporting bugs
11
+ * by suggesting new features
12
+ * by writing or editing documentation
13
+ * by writing specifications
14
+ * by writing code ( **no patch is too small**: fix typos, add comments, clean up
15
+ inconsistent whitespace)
16
+ * by refactoring code
17
+ * by closing [issues][]
18
+ * by reviewing patches
19
+
20
+ [issues]: https://github.com/moneydesktop/rubiks/issues
21
+
22
+ ## Submitting an Issue
23
+ We use the [GitHub issue tracker][issues] to track bugs and features. Before
24
+ submitting a bug report or feature request, check to make sure it hasn't
25
+ already been submitted. When submitting a bug report, please include a [Gist][]
26
+ that includes a stack trace and any details that may be necessary to reproduce
27
+ the bug, including your gem version, Ruby version, and operating system.
28
+ Ideally, a bug report should include a pull request with failing specs.
29
+
30
+ [gist]: https://gist.github.com/
31
+
32
+ ## Submitting a Pull Request
33
+ 1. [Fork the repository.][fork]
34
+ 2. [Create a topic branch.][branch]
35
+ 3. Add specs for your unimplemented feature or bug fix.
36
+ 4. Run `bundle exec rake spec`. If your specs pass, return to step 3.
37
+ 5. Implement your feature or bug fix.
38
+ 6. Run `bundle exec rake spec`. If your specs fail, return to step 5.
39
+ 7. Run `open coverage/index.html`. If your changes are not completely covered
40
+ by your tests, return to step 3.
41
+ 8. Add, commit, and push your changes.
42
+ 9. [Submit a pull request.][pr]
43
+
44
+ [fork]: http://help.github.com/fork-a-repo/
45
+ [branch]: http://learn.github.com/p/branching.html
46
+ [pr]: http://help.github.com/send-pull-requests/
data/Gemfile CHANGED
@@ -7,13 +7,16 @@ gemspec
7
7
  # so CI can include just test dependencies
8
8
  group :test do
9
9
  gem 'rake'
10
+ gem 'yard'
11
+ gem 'coveralls', :require => false
12
+ gem 'simplecov', :require => false
10
13
  gem 'rspec'
11
14
  gem 'rspec-pride'
12
15
  end
13
16
 
14
17
  group :development do
15
18
  gem 'awesome_print'
19
+ gem 'kramdown'
16
20
  gem 'pry'
17
21
  gem 'pry-nav'
18
- gem 'simplecov'
19
22
  end
@@ -1,5 +1,4 @@
1
- Copyright 2013 MoneyDesktop Inc.
2
- Copyright Rubiks contributors https://github.com/moneydesktop/rubiks/contributors
1
+ Copyright (c) 2013 MoneyDesktop Inc.
3
2
 
4
3
  MIT License
5
4
 
data/README.md CHANGED
@@ -1,10 +1,18 @@
1
1
  # Rubiks
2
2
 
3
- [![Gem Version](https://badge.fury.io/rb/rubiks.png)](http://badge.fury.io/rb/rubiks)
4
- [![Build Status](https://secure.travis-ci.org/moneydesktop/rubiks.png?branch=master)](https://travis-ci.org/moneydesktop/rubiks)
5
- [![Code Climate](https://codeclimate.com/github/moneydesktop/rubiks.png)](https://codeclimate.com/github/moneydesktop/rubiks)
3
+ [![Gem Version](https://badge.fury.io/rb/rubiks.png)][gem]
4
+ [![Build Status](https://secure.travis-ci.org/moneydesktop/rubiks.png?branch=master)][travis]
5
+ [![Dependency Status](https://gemnasium.com/moneydesktop/rubiks.png?travis)][gemnasium]
6
+ [![Code Climate](https://codeclimate.com/github/moneydesktop/rubiks.png)][codeclimate]
7
+ [![Coverage Status](https://coveralls.io/repos/moneydesktop/rubiks/badge.png?branch=master)][coveralls]
6
8
 
7
- Rubiks is a Ruby gem that defines an OLAP schema with `#to_xml` to generate a Mondrian XML schema and `#to_json` for everything else.
9
+ [gem]: https://rubygems.org/gems/rubiks
10
+ [travis]: http://travis-ci.org/moneydesktop/rubiks
11
+ [gemnasium]: https://gemnasium.com/moneydesktop/rubiks
12
+ [codeclimate]: https://codeclimate.com/github/moneydesktop/rubiks
13
+ [coveralls]: https://coveralls.io/r/moneydesktop/rubiks
14
+
15
+ A gem to allow defining an OLAP schema from a hash and generating an XML schema for Mondrian.
8
16
 
9
17
  ## Installation
10
18
 
@@ -14,6 +22,91 @@ Or you can add the following to your Gemfile and run the `bundle` command to ins
14
22
 
15
23
  gem 'rubiks'
16
24
 
25
+ ## Examples
26
+
27
+ This is an example taken from the [Mondrian documentation](http://mondrian.pentaho.com/documentation/schema.php#Cubes_and_dimensions) (then simplified a little and modified to use some Rails/Rubiks conventions)
28
+
29
+ After installing the gem, fire up an IRB session with `irb` or `bundle exec irb` if you are using Bundler, and follow this (or even paste it into your session):
30
+
31
+ ```ruby
32
+ require 'rubiks/examples'
33
+
34
+ md = Rubiks::Examples::MondrianDocs.new
35
+
36
+ md.filename # => "/path/to/rubiks/examples/mondrian_docs.yml"
37
+
38
+ puts md.file_contents # see the YAML section below
39
+
40
+ puts md.to_xml # see the XML section below
41
+ ```
42
+
43
+ The [example YAML file](examples/mondrian_docs.yml) contents are:
44
+
45
+ ```yaml
46
+ cubes:
47
+ - name: sales
48
+ dimensions:
49
+ - name: date
50
+ hierarchies:
51
+ - name: year_quarter_month
52
+ levels:
53
+ - name: year
54
+ column: the_year
55
+ data_type: numeric
56
+
57
+ - name: quarter
58
+ data_type: string
59
+
60
+ - name: month
61
+ column: month_of_year
62
+ data_type: Numeric
63
+
64
+ measures:
65
+ - name: unit_sales
66
+ aggregator: sum
67
+ format_string: '#,###'
68
+
69
+ - name: store_sales
70
+ aggregator: sum
71
+ format_string: '#,###'
72
+
73
+ - name: store_cost
74
+ aggregator: sum
75
+ format_string: '#,###'
76
+
77
+ calculated_members:
78
+ - name: profit
79
+ dimension: measures
80
+ formula: '[Measures].[Store Sales] / [Measures].[Store Cost]'
81
+ format_string: '$#,##0.00'
82
+ ```
83
+
84
+ and the generated XML is:
85
+
86
+
87
+ ```xml
88
+ <?xml version="1.0" encoding="UTF-8"?>
89
+ <schema name="default">
90
+ <cube name="Sales">
91
+ <table name="view_sales"/>
92
+ <dimension name="Date" foreignKey="date_id">
93
+ <hierarchy name="Year Quarter Month" primaryKey="id" hasAll="true">
94
+ <table name="view_dates"/>
95
+ <level name="Year" column="the_year" type="Numeric"/>
96
+ <level name="Quarter" column="quarter" type="String"/>
97
+ <level name="Month" column="month_of_year" type="Numeric"/>
98
+ </hierarchy>
99
+ </dimension>
100
+ <measure name="Unit Sales" aggregator="sum" formatString="#,###" column="unit_sales"/>
101
+ <measure name="Store Sales" aggregator="sum" formatString="#,###" column="store_sales"/>
102
+ <measure name="Store Cost" aggregator="sum" formatString="#,###" column="store_cost"/>
103
+ <calculatedMember name="Profit" dimension="Measures" formula="[Measures].[Store Sales] / [Measures].[Store Cost]">
104
+ <calculatedMemberProperty name="FORMAT_STRING" value="$#,##0.00"/>
105
+ </calculatedMember>
106
+ </cube>
107
+ </schema>
108
+ ```
109
+
17
110
  ## Contributing
18
111
 
19
112
  1. Fork it
data/Rakefile CHANGED
@@ -1,5 +1,12 @@
1
- require 'rspec/core/rake_task'
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
2
3
 
4
+ require 'rspec/core/rake_task'
5
+ desc 'Run all specs'
3
6
  RSpec::Core::RakeTask.new(:spec)
4
7
 
5
8
  task :default => :spec
9
+ task :test => :spec
10
+
11
+ require 'yard'
12
+ YARD::Rake::YardocTask.new
@@ -0,0 +1,36 @@
1
+ cubes:
2
+ - name: sales
3
+ dimensions:
4
+ - name: date
5
+ hierarchies:
6
+ - name: year_quarter_month
7
+ levels:
8
+ - name: year
9
+ column: the_year
10
+ data_type: numeric
11
+
12
+ - name: quarter
13
+ data_type: string
14
+
15
+ - name: month
16
+ column: month_of_year
17
+ data_type: Numeric
18
+
19
+ measures:
20
+ - name: unit_sales
21
+ aggregator: sum
22
+ format_string: '#,###'
23
+
24
+ - name: store_sales
25
+ aggregator: sum
26
+ format_string: '#,###'
27
+
28
+ - name: store_cost
29
+ aggregator: sum
30
+ format_string: '#,###'
31
+
32
+ calculated_members:
33
+ - name: profit
34
+ dimension: measures
35
+ formula: '[Measures].[Store Sales] / [Measures].[Store Cost]'
36
+ format_string: '$#,##0.00'
@@ -0,0 +1,61 @@
1
+ require 'rubiks'
2
+ require 'yaml'
3
+
4
+ module ::Rubiks
5
+ module Examples
6
+
7
+ # This example is taken from the Mondrian documentation:
8
+ # http://mondrian.pentaho.com/documentation/schema.php#Cubes_and_dimensions
9
+ # then modified to use Rails/Rubiks conventions
10
+ #
11
+ # We want the output to be:
12
+ #
13
+ # <?xml version="1.0" encoding="UTF-8"?>
14
+ # <schema name="default">
15
+ # <cube name="Sales">
16
+ # <table name="view_sales"/>
17
+ #
18
+ # <dimension name="Date" foreignKey="date_id">
19
+ # <hierarchy name="Year Quarter Month" primaryKey="id" hasAll="true">
20
+ # <table name="view_dates"/>
21
+ # <level name="Year" column="the_year" type="Numeric"/>
22
+ # <level name="Quarter" column="quarter" type="String"/>
23
+ # <level name="Month" column="month_of_year" type="Numeric"/>
24
+ # </hierarchy>
25
+ # </dimension>
26
+ #
27
+ # <measure name="Unit Sales" aggregator="sum" formatString="#,###" column="unit_sales"/>
28
+ # <measure name="Store Sales" aggregator="sum" formatString="#,###" column="store_sales"/>
29
+ # <measure name="Store Cost" aggregator="sum" formatString="#,###" column="store_cost"/>
30
+ #
31
+ # <calculatedMember name="Profit" dimension="Measures" formula="[Measures].[Store Sales] / [Measures].[Store Cost]">
32
+ # <calculatedMemberProperty name="FORMAT_STRING" value="$#,##0.00"/>
33
+ # </calculatedMember>
34
+ # </cube>
35
+ # </schema>
36
+ class MondrianDocs
37
+ class << self
38
+ def filename
39
+ File.expand_path('../../../examples/mondrian_docs.yml', __FILE__)
40
+ end
41
+
42
+ def file_contents
43
+ File.read(self.filename)
44
+ end
45
+
46
+ def hash
47
+ YAML.load_file(self.filename)
48
+ end
49
+
50
+ def schema
51
+ ::Rubiks::Schema.new_from_hash(self.hash)
52
+ end
53
+
54
+ def to_xml
55
+ self.schema.to_xml
56
+ end
57
+ end
58
+ end
59
+
60
+ end
61
+ end
@@ -16,5 +16,11 @@ module ::Rubiks
16
16
  self.name = name_value.to_s
17
17
  end
18
18
 
19
+ def display_name
20
+ return if self.name.nil?
21
+
22
+ self.name.titleize
23
+ end
24
+
19
25
  end
20
26
  end
@@ -0,0 +1,81 @@
1
+ require 'rubiks/nodes/validated_node'
2
+
3
+ module ::Rubiks
4
+
5
+ class CalculatedMember < ::Rubiks::AnnotatedNode
6
+ value :dimension, String
7
+ value :formula, String
8
+ value :format_string, String
9
+
10
+ validates :dimension_present, :formula_present
11
+
12
+ def self.new_from_hash(hash={})
13
+ new_instance = new
14
+ return new_instance.from_hash(hash)
15
+ end
16
+
17
+ def from_hash(working_hash)
18
+ return self if working_hash.nil?
19
+ working_hash.stringify_keys!
20
+
21
+ parse_name(working_hash.delete('name'))
22
+ parse_dimension(working_hash.delete('dimension'))
23
+ parse_formula(working_hash.delete('formula'))
24
+ parse_format_string(working_hash.delete('format_string'))
25
+ return self
26
+ end
27
+
28
+ def to_hash
29
+ hash = {}
30
+
31
+ hash['name'] = self.name.to_s if self.name.present?
32
+ hash['dimension'] = self.dimension.to_s if self.dimension.present?
33
+ hash['formula'] = self.formula.to_s if self.formula.present?
34
+ hash['format_string'] = self.format_string.to_s if self.format_string.present?
35
+
36
+ return hash
37
+ end
38
+
39
+ def dimension_present
40
+ errors << 'Dimension required on CalculatedMember' if self.dimension.blank?
41
+ end
42
+
43
+ def parse_dimension(dimension_value)
44
+ return if dimension_value.nil?
45
+
46
+ self.dimension = dimension_value.to_s
47
+ end
48
+
49
+ def formula_present
50
+ errors << 'Formula required on CalculatedMember' if self.formula.blank?
51
+ end
52
+
53
+ def parse_formula(formula_value)
54
+ return if formula_value.nil?
55
+
56
+ self.formula = formula_value.to_s
57
+ end
58
+
59
+ def parse_format_string(format_string_value)
60
+ return if format_string_value.nil?
61
+
62
+ self.format_string = format_string_value.to_s
63
+ end
64
+
65
+ def to_xml(builder = nil)
66
+ builder = Builder::XmlMarkup.new(:indent => 2) if builder.nil?
67
+
68
+ attrs = self.to_hash
69
+ attrs['name'] = self.name.titleize if self.name.present?
70
+ attrs['dimension'] = self.dimension.titleize if self.dimension.present?
71
+ attrs.delete('format_string')
72
+ builder.calculatedMember(attrs) do
73
+ if self.format_string.present?
74
+ format_attrs = {'name' => 'FORMAT_STRING', 'value' => self.format_string}
75
+ builder.calculatedMemberProperty(format_attrs)
76
+ end
77
+ end
78
+ end
79
+ end
80
+
81
+ end
@@ -1,17 +1,24 @@
1
1
  require 'rubiks/nodes/annotated_node'
2
2
  require 'rubiks/nodes/dimension'
3
3
  require 'rubiks/nodes/measure'
4
+ require 'rubiks/nodes/calculated_member'
4
5
 
5
6
  module ::Rubiks
6
7
 
7
8
  class Cube < ::Rubiks::AnnotatedNode
9
+ value :date_dimension, String
10
+ value :person_dimension, String
11
+ value :count_measure, String
12
+ value :person_count_measure, String
13
+
8
14
  child :dimensions, [::Rubiks::Dimension]
9
15
  child :measures, [::Rubiks::Measure]
16
+ child :calculated_members, [::Rubiks::CalculatedMember]
10
17
 
11
- validates :dimensions_present, :measures_present
18
+ validates :dimensions_present, :measures_present, :calculated_members_if_present
12
19
 
13
20
  def self.new_from_hash(hash={})
14
- new_instance = new('',[],[])
21
+ new_instance = new('','','','','',[],[],[])
15
22
  return new_instance.from_hash(hash)
16
23
  end
17
24
 
@@ -20,11 +27,57 @@ module ::Rubiks
20
27
  working_hash.stringify_keys!
21
28
 
22
29
  parse_name(working_hash.delete('name'))
30
+ parse_date_dimension(working_hash.delete('date_dimension'))
31
+ parse_person_dimension(working_hash.delete('person_dimension'))
32
+ parse_count_measure(working_hash.delete('count_measure'))
33
+ parse_person_count_measure(working_hash.delete('person_count_measure'))
23
34
  parse_dimensions(working_hash.delete('dimensions'))
24
35
  parse_measures(working_hash.delete('measures'))
36
+ parse_calculated_members(working_hash.delete('calculated_members'))
25
37
  return self
26
38
  end
27
39
 
40
+ def parse_date_dimension(input_value)
41
+ return if input_value.nil?
42
+
43
+ self.date_dimension = input_value.to_s.underscore
44
+ end
45
+
46
+ def parse_person_dimension(input_value)
47
+ return if input_value.nil?
48
+
49
+ self.person_dimension = input_value.to_s.underscore
50
+ end
51
+
52
+ def parse_count_measure(input_value)
53
+ return if input_value.nil?
54
+
55
+ self.count_measure = input_value.to_s.underscore
56
+ end
57
+
58
+ def parse_person_count_measure(input_value)
59
+ return if input_value.nil?
60
+
61
+ self.person_count_measure = input_value.to_s.underscore
62
+ end
63
+
64
+ def calculated_members_if_present
65
+ if self.calculated_members.present?
66
+ self.calculated_members.each do |calculated_member|
67
+ calculated_member.validate
68
+ errors.push(*calculated_member.errors)
69
+ end
70
+ end
71
+ end
72
+
73
+ def parse_calculated_members(calculated_members_array)
74
+ return if calculated_members_array.nil? || calculated_members_array.empty?
75
+
76
+ calculated_members_array.each do |calculated_member_hash|
77
+ self.calculated_members << ::Rubiks::CalculatedMember.new_from_hash(calculated_member_hash)
78
+ end
79
+ end
80
+
28
81
  def measures_present
29
82
  if self.measures.present?
30
83
  self.measures.each do |measure|
@@ -69,9 +122,28 @@ module ::Rubiks
69
122
  hash['name'] = self.name.to_s if self.name.present?
70
123
  hash['dimensions'] = self.dimensions.map(&:to_hash) if self.dimensions.present?
71
124
  hash['measures'] = self.measures.map(&:to_hash) if self.measures.present?
125
+ hash['calculated_members'] = self.calculated_members.map(&:to_hash) if self.calculated_members.present?
126
+ hash['date_dimension'] = self.date_dimension if self.date_dimension.present?
127
+ hash['person_dimension'] = self.person_dimension if self.person_dimension.present?
128
+ hash['count_measure'] = self.count_measure if self.count_measure.present?
129
+ hash['person_count_measure'] = self.person_count_measure if self.person_count_measure.present?
72
130
 
73
131
  return hash
74
132
  end
133
+
134
+ def to_xml(builder = nil)
135
+ builder = Builder::XmlMarkup.new(:indent => 2) if builder.nil?
136
+
137
+ attrs = Hash.new
138
+ attrs['name'] = self.name.titleize if self.name.present?
139
+ builder.cube(attrs) {
140
+ builder.table('name' => "view_#{self.name.tableize}") if self.name.present?
141
+
142
+ self.dimensions.each{ |dim| dim.to_xml(builder) } if self.dimensions.present?
143
+ self.measures.each{ |measure| measure.to_xml(builder) } if self.measures.present?
144
+ self.calculated_members.each{ |calculated_member| calculated_member.to_xml(builder) } if self.calculated_members.present?
145
+ }
146
+ end
75
147
  end
76
148
 
77
149
  end
@@ -37,6 +37,7 @@ module ::Rubiks
37
37
  return if hierarchies_array.nil? || hierarchies_array.empty?
38
38
 
39
39
  hierarchies_array.each do |hierarchy_hash|
40
+ hierarchy_hash['dimension'] = self.name if self.name.present?
40
41
  self.hierarchies << ::Rubiks::Hierarchy.new_from_hash(hierarchy_hash)
41
42
  end
42
43
  end
@@ -49,6 +50,23 @@ module ::Rubiks
49
50
 
50
51
  return hash
51
52
  end
53
+
54
+ def to_xml(builder = nil)
55
+ builder = Builder::XmlMarkup.new(:indent => 2) if builder.nil?
56
+
57
+ attrs = self.to_hash
58
+ attrs.delete('hierarchies')
59
+ attrs['name'] = self.name.titleize if self.name.present?
60
+ attrs.keys.each do |key|
61
+ attrs[key.camelize(:lower)] = attrs.delete(key)
62
+ end
63
+ attrs['foreignKey'] = "#{self.name.underscore}_id" if self.name.present?
64
+ builder.dimension(attrs) {
65
+ self.hierarchies.each do |hier|
66
+ hier.to_xml(builder)
67
+ end if self.hierarchies.present?
68
+ }
69
+ end
52
70
  end
53
71
 
54
72
  end