gooddata 0.6.0.pre9 → 0.6.0.pre10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. data/.gitignore +3 -0
  2. data/.rspec +3 -0
  3. data/.yardopts +19 -0
  4. data/Gemfile +5 -0
  5. data/README.md +191 -0
  6. data/Rakefile +18 -1
  7. data/bin/gooddata +74 -63
  8. data/doc/.gitignore +1 -0
  9. data/doc/css/.gitkeepme +1 -0
  10. data/doc/images/.gitkeepme +1 -0
  11. data/doc/images/background.png +0 -0
  12. data/doc/images/bg-callout-button.png +0 -0
  13. data/doc/images/header-logo.png +0 -0
  14. data/doc/images/logo-image.png +0 -0
  15. data/doc/js/.gitkeepme +1 -0
  16. data/doc/pages/GET_STARTED.md +309 -0
  17. data/doc/pages/HOMEPAGE.md +75 -0
  18. data/doc/pages/TUTORIALS.md +52 -0
  19. data/doc/pages/tutorial/BRICKS.md +257 -0
  20. data/doc/pages/tutorial/CREATING_A_MODEL.md +79 -0
  21. data/doc/pages/tutorial/CRUNCHING_NUMBERS.md +233 -0
  22. data/doc/pages/tutorial/TEST_DRIVEN_DEVELOPMENT.md +118 -0
  23. data/doc/pages/tutorial/YOUR_FIRST_PROJECT.md +52 -0
  24. data/doc/templates/default/class/dot/setup.rb +6 -0
  25. data/doc/templates/default/class/dot/superklass.erb +3 -0
  26. data/doc/templates/default/class/setup.rb +36 -0
  27. data/doc/templates/default/class/text/setup.rb +11 -0
  28. data/doc/templates/default/class/text/subclasses.erb +5 -0
  29. data/doc/templates/default/constant/text/header.erb +11 -0
  30. data/doc/templates/default/constant/text/setup.rb +3 -0
  31. data/doc/templates/default/docstring/setup.rb +51 -0
  32. data/doc/templates/default/docstring/text/abstract.erb +2 -0
  33. data/doc/templates/default/docstring/text/deprecated.erb +2 -0
  34. data/doc/templates/default/docstring/text/index.erb +2 -0
  35. data/doc/templates/default/docstring/text/note.erb +4 -0
  36. data/doc/templates/default/docstring/text/private.erb +2 -0
  37. data/doc/templates/default/docstring/text/returns_void.erb +1 -0
  38. data/doc/templates/default/docstring/text/text.erb +1 -0
  39. data/doc/templates/default/docstring/text/todo.erb +4 -0
  40. data/doc/templates/default/layout/dot/header.erb +6 -0
  41. data/doc/templates/default/layout/dot/setup.rb +14 -0
  42. data/doc/templates/default/method/setup.rb +3 -0
  43. data/doc/templates/default/method/text/header.erb +1 -0
  44. data/doc/templates/default/method_details/setup.rb +10 -0
  45. data/doc/templates/default/method_details/text/header.erb +10 -0
  46. data/doc/templates/default/method_details/text/method_signature.erb +12 -0
  47. data/doc/templates/default/method_details/text/setup.rb +10 -0
  48. data/doc/templates/default/module/dot/child.erb +1 -0
  49. data/doc/templates/default/module/dot/dependencies.erb +3 -0
  50. data/doc/templates/default/module/dot/header.erb +6 -0
  51. data/doc/templates/default/module/dot/info.erb +14 -0
  52. data/doc/templates/default/module/dot/setup.rb +14 -0
  53. data/doc/templates/default/module/setup.rb +164 -0
  54. data/doc/templates/default/module/text/children.erb +10 -0
  55. data/doc/templates/default/module/text/class_meths_list.erb +8 -0
  56. data/doc/templates/default/module/text/extends.erb +8 -0
  57. data/doc/templates/default/module/text/header.erb +7 -0
  58. data/doc/templates/default/module/text/includes.erb +8 -0
  59. data/doc/templates/default/module/text/instance_meths_list.erb +8 -0
  60. data/doc/templates/default/module/text/setup.rb +12 -0
  61. data/doc/templates/default/root/dot/child.erb +3 -0
  62. data/doc/templates/default/root/dot/setup.rb +5 -0
  63. data/doc/templates/default/tags/setup.rb +55 -0
  64. data/doc/templates/default/tags/text/example.erb +12 -0
  65. data/doc/templates/default/tags/text/index.erb +1 -0
  66. data/doc/templates/default/tags/text/option.erb +20 -0
  67. data/doc/templates/default/tags/text/overload.erb +19 -0
  68. data/doc/templates/default/tags/text/see.erb +11 -0
  69. data/doc/templates/default/tags/text/tag.erb +13 -0
  70. data/examples.rb +2 -2
  71. data/gooddata.gemspec +31 -26
  72. data/lib/gooddata/bricks/middleware/gooddata_middleware.rb +1 -1
  73. data/lib/gooddata/client.rb +65 -53
  74. data/lib/gooddata/commands/commands.rb +9 -0
  75. data/lib/gooddata/commands/process.rb +9 -8
  76. data/lib/gooddata/commands/projects.rb +29 -0
  77. data/lib/gooddata/commands/runners.rb +1 -1
  78. data/lib/gooddata/connection.rb +6 -4
  79. data/lib/gooddata/exceptions.rb +2 -1
  80. data/lib/gooddata/helpers.rb +1 -1
  81. data/lib/gooddata/model.rb +360 -189
  82. data/lib/gooddata/models/metadata.rb +1 -1
  83. data/lib/gooddata/models/metric.rb +2 -1
  84. data/lib/gooddata/models/project.rb +1 -1
  85. data/lib/gooddata/models/report.rb +0 -18
  86. data/lib/gooddata/version.rb +1 -1
  87. data/spec/blueprint_spec.rb +83 -43
  88. data/spec/data/additional_dataset_module.json +18 -0
  89. data/spec/data/blueprint_invalid.json +36 -0
  90. data/spec/data/blueprint_valid.json +37 -0
  91. data/spec/data/model_module.json +18 -0
  92. data/spec/{test_project_model_spec.json → data/test_project_model_spec.json} +4 -0
  93. data/spec/full_project_spec.rb +4 -3
  94. data/spec/helpers/blueprint_helper.rb +17 -0
  95. data/spec/merging_blueprints_spec.rb +23 -48
  96. data/spec/model_dsl_spec.rb +2 -2
  97. data/spec/model_spec.rb +44 -0
  98. data/spec/project_build_and_update_spec.rb +28 -0
  99. data/spec/spec_helper.rb +6 -0
  100. data/yard-server.sh +3 -0
  101. metadata +251 -74
  102. data/README.rdoc +0 -176
@@ -0,0 +1,257 @@
1
+ You can run Ruby on GoodData platform. Let's have a look at the platform and walk step by step through doing the simplest possible deployment and then move to more advanced tasks.
2
+
3
+ ## Not reinventing the wheel
4
+
5
+ The main idea is that only minority of people should be forced to write code. The others should be happy running them without understanding the details. Soon we will introduce better UI to do just that. Until then it is more programatic but if you are not scared read on.
6
+
7
+ ## Setting up the stage
8
+
9
+ GoodData Ruby SDK stack is built so you can easily develop things locally and deploy them when you have tested them and are happy with how they work. You need to set up an environment first.
10
+
11
+ ### Prerequisites
12
+ * Git
13
+ * Ruby (JRuby recommended)
14
+ * Ruby Gems
15
+
16
+ Go ahead and run
17
+
18
+ git clone https://github.com/gooddata/app_store.git
19
+
20
+ We just cloned the remote repository which contains information about the used libraries and also contains some stuff that others created. We will investigate that later. Let's continue with setting things up.
21
+
22
+ Run
23
+
24
+ gem install bundler
25
+
26
+ This will install bundler which is a useful package installation tool. Let's use it
27
+
28
+ cd local_repo
29
+ bundle install --binstubs
30
+
31
+ This will ensure that you have installed exactly what we have on the production machines. This should mitigate bugs caused by slightly different versions of libraries and incompatible APIs.
32
+
33
+ You are ready to go.
34
+
35
+ ## Running your first brick
36
+
37
+ The small pieces of ruby that are run on the platform are called bricks. Nobody knows where and why this name emerged but there are rumors that it is supposed to reference the fact that out of brick just laid together you can create a solid wall.
38
+
39
+ If you open the repository you will see there some directories. Find directory called misc/hello_world and open it. It contains only one file. Do not open it yet.
40
+
41
+ Run this in console.
42
+
43
+ bundle exec gooddata -l -Uname@gooddata.com -Pmy_pass run_ruby -p project_pid -d hello_world_brick --name "some_deploy" --remote
44
+
45
+ It will take some time but after a while you should see green DONE on your console and a link for the log. Open it in your browser and you should see there something like this. On one of the lines there should be hello world. Great you just ran your first ruby brick.
46
+
47
+ ###Looking inside Hello World
48
+
49
+ Let's see what is happening inside. Open the main.rb file in your favorite editor. You should see something like this.
50
+
51
+ require 'gooddata'
52
+ require 'logger'
53
+
54
+ module GoodData::Bricks
55
+ class HelloWorldBrick
56
+
57
+ def call(params)
58
+ logger = Logger.new(params[:GDC_LOGGER_FILE])
59
+ logger.info "Hello world"
60
+ end
61
+
62
+ end
63
+ end
64
+
65
+ GoodData::Bricks::HelloWorldBrick.new
66
+
67
+ This is all. Let's dissect it. The interface is very simple. You have to provide an instance of an object that responds to a message :call (in other words does implement method call). This method takes one parameter and that is a hash map of parameters.
68
+
69
+ You can see that we have implemented such a class and returning an instance of that class. The method accepts params and you can see that we immediately make use of them when grabbing logger and the writing to it. Platform does the heavy lifting on the back and you have already seen the result.
70
+
71
+ ## Digging deeper
72
+
73
+ It might be surprising if I tell you that this is not exactly how majority of the real bricks are implemented. What we showed you is fine and this is how we started but after we implemented some bricks we found out that we are repeating ourselves a lot. So we tried to come up with something better.
74
+
75
+ remark: If you know how Rack or any similar framework works for abstracting web applications you would be right at home since that is where most of the inspiration came from.
76
+
77
+ We introduced three concepts.
78
+
79
+ * _Application_ - This part is responsible for doing the core of the task you are interested in. Structure of an app is pretty much what you have already seen.
80
+ * _Middleware_ - very similar to app. The main difference is that you can chain them together. The main similarity is that it has the same interface as an app
81
+ * _Pipelines_ - If you chain multiple middleweres and applications it creates a pipeline.
82
+
83
+ Let's have a look how it works visually.
84
+
85
+ ![Example pipeline consisting of 3 middlewares](https://dl.dropboxusercontent.com/s/g5rymdmmx97hc61/middlewares.png?token_hash=AAE7qAjkOxA6tQGDk8UY17ltRu0ZG5UqwSJ_8ZtAl7ZNaA)
86
+
87
+ This is a simple pipeline with 2 middlewares and one app. The arrows depict how the execution order would flow.
88
+
89
+ ### Executing a pipeline
90
+ Your pipeline is executed. First middleware is called then the second and third. Then your app is called it does what it needs to and then the call goes back through the middlewares (so they can actually act twice).
91
+
92
+ This probably does not seem that much useful so let's have a look at couple of examples where you might use it.
93
+
94
+ Plumbing - just the plumbing. Did you notice how we had to set up the logger in our Hello World example? It is not a lot of code but imagine that you need to do 10 things like this. It can bog you down. There are couple of middlewares that try to help you with similar stuff. It is similar to what AOP style of programming tries to do.
95
+
96
+ Examples
97
+
98
+ * log in to various systems and prepeare for action
99
+ * Set up loggers
100
+
101
+ Decorators - Imagine that you have done something great. For example computing hierarchy of people from some information. It is so much useful that you would like to let other people use it. But everybody has slightly different use case. Somebody wants to output it to web dav or s3 storage. Somebody want's to tweet about that it finished somebody might want to store this file into vertica. Implementing serialization in a separate middlewere means that you do not need to touch the actually code that.
102
+
103
+ * measuring time
104
+ * serializing stuff to various places
105
+ * letting other people know
106
+
107
+ ### First pipeline
108
+
109
+ Ok let's create our first pipeline. Let's open misc/hello_world_pipeline_brick in your browser
110
+
111
+ You will see two files. Let's check the hello_world.rb first.
112
+
113
+ module MyFirstBrick
114
+
115
+ class HelloWorldBrick
116
+ def call(params)
117
+ logger = params[:gdc_logger]
118
+ logger.info "Hello world"
119
+ end
120
+ end
121
+
122
+ end
123
+
124
+ It looks exactly the same as in previosus case except for the logger setting up. Let's look at the other file main.rb
125
+
126
+ You see that at the top we are requiring the hello_world.rb and then we are setting up the pipeline. You can see that even this basic example uses quite a bit of middleare but hopefully their names are fairly self describing. Logging will hook up a logger. GoodData logs you in to GD and hooks the library to the logger if you want to. Timinng will simply measure the execution of the app itself.
127
+
128
+ Notice several things. Pipeline has exactly one app. It also has to be the last one in the pipeline. It has zero or more middlewares. When constructong a pipeline you can specify a stage either via a class or an instance. If it is a class we instatntiate it for you (without parameters). This is useful if you want to parametrize the middleware somehow.
129
+
130
+ ## Middlewares
131
+ Let's have a look at some middlewares
132
+ App was not very challenging from programming perspective (which is the goal) and you will see that middleware is not any more complicated. There are generally two types of middleares. First is a middleware that wants to act only once. It is like fire and forget. For example setting up logger. You just create it and do not care. There is a second type and that is a middleware that wants to act twice. Once before the app itself was called. Second after an app was called. Typical example might be time measuring. You want to start your clock before the app itself is called but then you have to stop them sometimes. The difference is absolutely minimal let's walk through both of them.
133
+
134
+ ### Logger middleware
135
+ require 'logger'
136
+
137
+ module GoodData::Bricks
138
+ class LoggerMiddleware < GoodData::Bricks::Middleware
139
+
140
+ def call(params)
141
+ logger = params[:gdc_logger] = params[:GDC_LOGGER_FILE].nil? ? Logger.new(STDOUT) : Logger.new(params[:GDC_LOGGER_FILE])
142
+ logger.info("Pipeline starts")
143
+
144
+ returning(@app.call(params)) do |result|
145
+ logger.info("Pipeline ending")
146
+ end
147
+ end
148
+
149
+ end
150
+ end
151
+
152
+ ### Benchmarking middleware
153
+
154
+ require 'benchmark'
155
+
156
+ module GoodData::Bricks
157
+
158
+ class BenchMiddleware < GoodData::Bricks::Middleware
159
+
160
+ def call(params)
161
+ puts "Starting timer"
162
+ result = nil
163
+ report = Benchmark.measure { result = @app.call(params) }
164
+ puts "Stopping timer"
165
+ pp report
166
+ result
167
+ end
168
+
169
+ end
170
+ end
171
+
172
+ You see there are really only minimal changes. Let's walk through the couple of important points more carefully. As we stated before difference between app and a middleware is mainly in the fact that you can chain middlewares. Thus middleware has to know who is the next on in the chain and at some point it is going to call him. That is the `@app.call(params)`. Notice how we are still using the same interface. This way it is no difference if the next guy is app or middleware. The second important piece might be the returning function but that is just a way how to be more dry. Returning will take one param evaluate it store it you can do some stuff on it and then its value is returned. These things are equivalent.
173
+
174
+ returning(Person.new) do |o|
175
+ p.name = "Tomas"
176
+ end
177
+
178
+ p = Person.new
179
+ p.name = "Tomas"
180
+ p
181
+
182
+ ### Passing values
183
+
184
+ We haven't talk much explicitly about passing values. The recommended way how to pass parameters is through the `:call(params)` methods parameters. You can see example of that for example in the logger middleware. It creates a logger and puts it into the param object. All following middlewares that are called can benefit from it. You can again see the usage in the HelloWorld.
185
+
186
+ ### Providing initial sets of parameters
187
+
188
+ When deploying you will have to either provide parameters during execution or to the scheduler. Since we are developing locally it would be great to have similar functionality during local execution. This is exactly what `--params` parameter does.
189
+
190
+ bundle exec gooddata -v run_ruby --remote asdas/asdadas --params path_to_params_file.json
191
+
192
+ The file is simple JSON file. This file is used in both local or remote execution.
193
+
194
+ {
195
+ "param1" : "value1",
196
+ "param2" : "value2"
197
+ }
198
+
199
+ ### Return parameters
200
+
201
+ ## Local harness
202
+ Let's talk a little about the harness that makes it possible to test things locally. It tries to run your bricks in similar environment as it would run on the server. This mainly means that you will get the same parameters etc. The goal is to provide you environment to debug and test before you deploy.
203
+
204
+
205
+ ### Run locally
206
+
207
+ The most complete command
208
+
209
+ bundle exec gooddata -l -s https://secure.gooddata.com -Uname@gooddata.com -Pmy_pass -w https://secure-di.getgooddata.com run_ruby -p rjzbt1shubkj9c8es6f75t2avke748mj -d brick_test --name "some_deploy"
210
+
211
+ looks intimidating but let's break it down. Many of these can be omitted and are there in case you can override everything.
212
+
213
+ bundle exec
214
+
215
+ means you are executing it with exact the same libraries as you would on the server (we are expecting that your local repo is up to date).
216
+
217
+ -l
218
+
219
+ Means that HTTP communication will be logged to STDOUT.
220
+
221
+ -s https://secure.gooddata.com
222
+
223
+ Means which datacenter you are connecting to. If you are not going against something special you should be able to leave this out
224
+
225
+ -w https://secure-di.getgooddata.com
226
+
227
+ File staging area used for uploading the files for execution. Similar case as for he server. By default you should not be forced to use it.
228
+
229
+ -p PID
230
+
231
+ Currently only execution context we have is project context. You have to specify a project PID to be abel to run your brick
232
+
233
+ -d path_to_brick
234
+
235
+ This tells the tool where brick lives. It expect the directory where the main.rb lives. Do not point this directly to a file.
236
+
237
+ --name some_name
238
+
239
+ Used when deployed to name the process so later you can identify it
240
+
241
+ ### Run remotely
242
+
243
+ The only change to do when you are running things remotely is to add `--remote` to the command. Everything else should remain the same.
244
+
245
+ <div class="section-nav">
246
+ <div class="left align-right">
247
+ <a href="/docs/file/doc/pages/tutorial/TEST_DRIVEN_DEVELOPMENT.md" class="prev">
248
+ Back
249
+ </a>
250
+ </div>
251
+ <div class="right align-left">
252
+
253
+ <span class="next disabled">Next</span>
254
+
255
+ </div>
256
+ <div class="clear"></div>
257
+ </div>
@@ -0,0 +1,79 @@
1
+ There are several ways how to express a model and create it in GoodData. The most prominent way to do it is the visual modeler that is part of the CloudConnect package. There are clear advantages like being visual but there are also drawbacks. It is not repeatable, it is not programmable and it is not text based. Let's have a look how to create a simple model using Ruby SDK.
2
+
3
+ ## The model
4
+ The model we will be creating is this
5
+
6
+ ## The code
7
+ Create a file called model.rb and put this inside.
8
+
9
+ GoodData::Model::ProjectBuilder.create("gooddata-ruby test #{Time.now.to_i}") do |p|
10
+ p.add_date_dimension("closed_date")
11
+
12
+ p.add_dataset("users") do |d|
13
+ d.add_anchor("id")
14
+ d.add_label("name", :reference => 'id')
15
+ end
16
+
17
+ p.add_dataset("regions") do |d|
18
+ d.add_anchor("id")
19
+ d.add_attribute("name")
20
+ end
21
+
22
+ p.add_dataset("opportunities") do |d|
23
+ d.add_fact("amount")
24
+ d.add_date("closed_date", :dataset => "closed_date")
25
+ d.add_reference("user_id", :dataset => 'users', :reference => 'id')
26
+ d.add_reference("region_id", :dataset => 'regions', :reference => 'id')
27
+ end
28
+ end
29
+
30
+ Hopefully the model is self descriptive and if you are not strong on terminology like label, anchor etc please refer to "Building a model with GD".
31
+
32
+ ## Some rules
33
+
34
+ Please note several things
35
+
36
+ * we are trying to apply several conventions if you follow them it will be less typing for you but you can always override them.
37
+ * in all the cases where you type a name it is a string that will be used to create a technical name in gooddata also called identifier on the API. The user visible name which we call title will be inferred if not provided. The inferring process is simple. We expect you to provide name in the snake case (as is typical in ruby, this means things like close_date, opportunity_dimension etc). These will be translated int human readable strings (Close date, Opportunity dimension). If you do not like the title you can specify it directly via :title => "My own title"`
38
+ * the names are also used as reference names in the model. Notice how the date is using name of the close_date dimension and also the user_id reference is using reference users
39
+
40
+ ## Executing the model
41
+
42
+ gooddata --username joe@example.com --password my_secret_pass --token my_token project build model.rb
43
+
44
+ ## Loading data
45
+ As part of the process we allow you to load data since sometimes some initial datasets should be part of the model and not ETL. The typical usecase is for the sake of defining reports which are filtered on certain values these values have to be present.
46
+
47
+ ### Loading data given inline
48
+
49
+ p.upload([["id", "name"],
50
+ ["1", "Tomas"],
51
+ ["2", "Petr"]], :dataset => 'users')
52
+
53
+
54
+ ### Loading data given by filename
55
+
56
+ p.upload("/some/local_file.csv", :dataset => "users")
57
+
58
+ ### Loading data given by web file
59
+
60
+ p.upload("http://www.example.com/some/remote_file.csv", :dataset => "users")
61
+
62
+ In all cases the file has to have headers that has the same name as the name of the particular columns (not necessarily in the same order).
63
+
64
+ <div class="section-nav">
65
+ <div class="left align-right">
66
+ <a href="/docs/file/doc/pages/tutorial/YOUR_FIRST_PROJECT.md" class="prev">
67
+ Back
68
+ </a>
69
+ </div>
70
+
71
+ <div class="right align-left">
72
+
73
+ <a href="/docs/file/doc/pages/tutorial/CRUNCHING_NUMBERS.md" class="next">
74
+ Next
75
+ </a>
76
+
77
+ </div>
78
+ <div class="clear"></div>
79
+ </div>
@@ -0,0 +1,233 @@
1
+ <div markdown="1">
2
+ This is *true* markdown text.
3
+ </div>
4
+
5
+ MAQL is a language that is fairly similar to SQL but it is aimed towards getting the data from OLAP system. You are never forced to talk about columns and specify joins explicitly. This is great but there are some drawbacks. Same as SQL MAQL is aimed towards users more than machines which does not help for automation but there is one more caveat that make it hard to use even for humans. Probably to your surprise
6
+
7
+ SELECT SUM(Amount)
8
+
9
+ Is not a MAQL statement (even though you probably have seen this on UI). The more correct (and what goes back and forth over the wire) is
10
+
11
+ SELECT SUM([/gdc/md/132131231/obj/1])
12
+
13
+ GoodData UI does a great job at hiding this complexity from you but this significantly hinders the use of MAQL over and API by regular Joes. Ruby SDK tries to alleviate the situation with some tricks. It also gives you many tools to programmatically define and deal with reports and lays the foundations for test driven BI.
14
+
15
+ ### Jack in
16
+ If you do not have a project best would be to create one by following our tutorial [Your first project](http://sdk.gooddata.com/gooddata-ruby/recipe/2014/01/19/your-first-project.html) so you can get predictable results.
17
+
18
+ First let's look around. There are no metrics
19
+
20
+ GoodData::Metric[:all]
21
+ > []
22
+
23
+ there is one fact
24
+
25
+ GoodData::Fact[:all]
26
+ > [{"link"=>"/gdc/md/ptbedvc1841r4obgptywd2mzhbwjsfyr/obj/223",
27
+ "author"=>"/gdc/account/profile/4e1e8cacc4989228e0ae531b30853248",
28
+ "tags"=>"",
29
+ "created"=>"2014-02-18 07:44:26",
30
+ "deprecated"=>"0",
31
+ "summary"=>"",
32
+ "title"=>"Lines changed",
33
+ "category"=>"fact",
34
+ "updated"=>"2014-02-18 07:44:26",
35
+ "contributor"=>"/gdc/account/profile/4e1e8cacc4989228e0ae531b30853248"}]
36
+
37
+ ### First metric
38
+ Let's create our first metric. There are couple of ways so I will show them one by one. Regardless of how you create the metric the result is the same so pick the one that suits your style or situation.
39
+
40
+ TBD(add identifier based metric)
41
+
42
+ m = GoodData::Metric.create("SELECT SUM([/gdc/md/ptbedvc1841r4obgptywd2mzhbwjsfyr/obj/223])")
43
+
44
+ You can do it like this but obviously this is the ugly verbose way.
45
+
46
+ m = GoodData::Metric.xcreate('SELECT SUM(#"Lines changed")')
47
+
48
+ Here you are using the name of the fact. Let's notice couple of things. First we are not using create any more. Method xcreate stands for eXtended notation and tries to turn it into valid MAQL. When you are specifying the fact you are doing it using #"NAME".
49
+
50
+ Regardless of which way you used you have a metric definition. Metric is only locally on your computer we haven't saved it yet. Let's do it.
51
+
52
+ m.save
53
+ > RuntimeError: Meric needs to have title
54
+
55
+ Uh ok. You have two options
56
+
57
+ m.title = "My shiny metric"
58
+
59
+ or
60
+
61
+ m = GoodData::Metric.xcreate(:title => "My shiny metric", :expression => 'SELECT SUM(#"Lines changed")')
62
+
63
+ Go ahead and try saving it again.
64
+
65
+ m.save
66
+ > #<GoodData::Metric:0x007f95b609b548 ....
67
+
68
+ Great, looks good. Let's see if it worked
69
+
70
+ m.saved?
71
+ > true
72
+
73
+ m.uri
74
+ > "/gdc/md/ptbedvc1841r4obgptywd2mzhbwjsfyr/obj/292"
75
+
76
+ Let's get some numbers. You can execute the metric.
77
+
78
+ m.execute
79
+ > 9.0
80
+
81
+ Fantastic. You just created your first report over API.
82
+
83
+ ### More on metric execution
84
+
85
+ Maybe you are wondering if you cannot just execute stuff to poke around. Well you kinda can. The API does not allow to execute a metric without it is saved (but we hope this will change soon). SDK tries to hide this from you but sometimes you can see the wiring. Let's explore. This is our well known metric
86
+
87
+ m = GoodData::Metric.xcreate('SELECT SUM(#"Lines changed")')
88
+
89
+ Let's try executing it
90
+
91
+ m.execute
92
+ > 9
93
+
94
+ It works. What happens behind the scenes is that SDK saves the metric and then deletes it again. It should mostly work
95
+
96
+ m.is_saved?
97
+ > false
98
+
99
+ m.uri
100
+ > nil
101
+
102
+ But sometimes you can see some inconsistencies.
103
+
104
+ m.title
105
+ > "Untitled metric"
106
+
107
+ This should not stop you most of the time. Just keep this in mind. Hopefully it will go completely away soon.
108
+
109
+ ### Defining a ReportDefinition
110
+
111
+ Ok we have our metric. Now it would be interesting to see a report. The metric broken down. If you are familiar with the model you know the metric is a summation of lines changed in all commits in all products made by all developers. Let's see, how developers contributed.
112
+
113
+ GoodData::ReportDefinition.execute(:top => ["attr.devs.id"], :left => [m])
114
+ > [ 1 | 2 | 3 ]
115
+ [1.0 | 3.0 | 5.0]
116
+
117
+ Again, there are a lots of ways how to achieve the same result so let's have a look at what is available right now. You can already see that you can see a metric by reference and attribute can be referenced just by a string containing an identifier. Let's pass the attribute as an object as well.
118
+
119
+ a = GoodData::Attribute.get_by_id("attr.devs.id")
120
+ GoodData::ReportDefinition.execute(:top => [a], :left => [m])
121
+
122
+ If you studied UI well you know that Report is defined using Display Forms (or labels) not by attribute. If you are specifying an attribute SDK will take the first one automatically. This works well most of the time since attributes have typically just one label. But sometimes they have many so you need to me more specific. Coincidently our attribute has 2 labels.
123
+
124
+ a.display_forms.count
125
+ > 2
126
+
127
+ The identifiers are "label.devs.email" and "label.devs.id". Let's try using those
128
+
129
+ GoodData::ReportDefinition.execute(:top => ["label.devs.id"], :left => [m])
130
+ GoodData::ReportDefinition.execute(:top => ["label.devs.email"], :left => [m])
131
+
132
+ You can even do something that you cannot do on UI and that is using both of the labels at the same time (sic).
133
+
134
+ GoodData::ReportDefinition.execute(:top => ["label.devs.id", "label.devs.email"], :left => [m])
135
+
136
+ In almost all above cases we had only one thing in left or top section. You can save your fingers and not use the array literal if there is only one item in the section. SDK will wrap them for you.
137
+
138
+ GoodData::ReportDefinition.execute(:top => "label.devs.id", :left => m)
139
+
140
+ Sometimes it might be useful to refer to the objects in a different way. You can do it by title
141
+
142
+ GoodData::ReportDefinition.execute(
143
+ :top => [{:type => :attribute, :title => "Month/Year (committed_on)"}],
144
+ :left => m)
145
+
146
+ Since underneath it uses MdObject.find_first_by_title it also accepts RegExp literal
147
+
148
+ GoodData::ReportDefinition.execute(
149
+ :top => [{:type => :attribute, :title => /Month\/Year/}],
150
+ :left => m)
151
+
152
+ GoodData::ReportDefinition.execute(
153
+ :top => [{:type => :attribute, :title => /month\/year/i}],
154
+ :left => m)
155
+
156
+ In our model we have two attributes of name Id. Since the title does not have to be unique this is ok. Currently it will pick the first for you but this behavior will likely change in the favor of throwing an ambiguous error much like your SQL client probably does.
157
+
158
+ GoodData::ReportDefinition.execute(:top => [{:type => :attribute, :title => "Id"}], :left => m)
159
+
160
+ Of course you can combine all things we learned together.
161
+
162
+ a = GoodData::Attribute.find_first_by_title(/month\/year/i)
163
+ GoodData::ReportDefinition.execute(:top => [{:type => :attribute, :title => "Id"}], :left => [m, a])
164
+
165
+ ### Reports
166
+ Up until now we have been computing the reports just because. Maybe you wonder how you can actually create a report that would be saved. It is simple
167
+
168
+ GoodData::ReportDefinition.execute(
169
+ :top => [{:type => :attribute, :title => /Month\/Year/}],
170
+ :left => m)
171
+
172
+ report = GoodData::Report.create(
173
+ :title => "Fantastic report",
174
+ :top => [{:type => :attribute, :title => /Month\/Year/}],
175
+ :left => m)
176
+
177
+ report.save
178
+
179
+ ### Results
180
+
181
+ Ok now we know how to create a report. Now let's see what we can do with the result. The point of this framework is not only you being able to create reports programmatically and save them for consumption over UI. Yes this is incredibly useful but when we have that why stop there. With Ruby SDK you can actually consume the result programmatically as well so you can use GD as a basis for your application. Or as we show in this section we build a foundation for Test Driven BI development.
182
+
183
+ Let's execute one of the previously defined reports and this time let's store the result
184
+
185
+ result = GoodData::ReportDefinition.execute(
186
+ :top => [{:type => :attribute, :title => /month\/year/i}],
187
+ :left => m)
188
+ >
189
+ [Jan 2014 | Feb 2014]
190
+ [1.0 | 8.0 ]
191
+
192
+ The class is ReportDataResult. As you will see further it tries to conform to API of an array in key aspoects
193
+
194
+ result.class
195
+ > GoodData::ReportDataResult
196
+
197
+ result[0][0]
198
+ > "Jan 2014"
199
+
200
+ result[1][0]
201
+ > 1.0
202
+
203
+ All the numbers are of BigDecimal class so you should be able to perform additional computations without losing precision
204
+
205
+ result[1][0].class
206
+ > BigDecimal
207
+
208
+ Let's look on couple of methods that are useful for validating the results of reports
209
+
210
+ result.include_row? [1, 8]
211
+ > true
212
+
213
+ result.include_column? ["Feb 2014", 8]
214
+ > true
215
+
216
+ Result is coming from server in a special format but after some processing it is just an 2 D array so it is no wonder that you can test on equality of a whole report.
217
+
218
+ result == [["Jan 2014", "Feb 2014"], [1, 8]]
219
+
220
+ <div class="section-nav">
221
+ <div class="left align-right">
222
+ <a href="/docs/file/doc/pages/tutorial/CREATING_A_MODEL.md" class="prev">
223
+ Back
224
+ </a>
225
+ </div>
226
+
227
+ <div class="right align-left">
228
+ <a href="/docs/file/doc/pages/tutorial/TEST_DRIVEN_DEVELOPMENT.md" class="next">
229
+ Next
230
+ </a>
231
+ </div>
232
+ <div class="clear"></div
233
+ </div>