rubiks 0.0.4 → 0.0.5

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.
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