rose 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ Autotest.add_hook :initialize do |at|
2
+ at.add_exception('spec/log')
3
+ end
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,24 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
22
+
23
+ .yardoc
24
+ doc
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 hsume2
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,271 @@
1
+ # rose
2
+
3
+ Rose (say it out loud: rows, rows, rows) is a slick Ruby DSL for reporting:
4
+
5
+ Rose.make(:worlds) do
6
+ rows do
7
+ column(:hello => "Hello")
8
+ column(:world)
9
+ end
10
+ end
11
+
12
+ class World < Struct.new(:hello, :world)
13
+ end
14
+
15
+ Rose(:worlds).bloom([World.new("Say", "what?")]).to_s
16
+
17
+ +---------------+
18
+ | Hello | world |
19
+ +---------------+
20
+ | Say | what? |
21
+ +---------------+
22
+
23
+ Install the gem:
24
+
25
+ gem install rose
26
+
27
+
28
+ *****
29
+
30
+ # Usage
31
+
32
+ ## Making a Report
33
+
34
+ class Flower < Struct.new(:type, :color, :age)
35
+ end
36
+
37
+ Rose.make(:poem, :class => Flower) do
38
+ rows do
39
+ column(:type => "Type")
40
+ column("Color", &:color)
41
+ end
42
+ end
43
+
44
+ ## Running a Report
45
+
46
+ Rose(:poem).bloom([Flower.new(:roses, :red), Flower.new(:violets, :blue)])
47
+
48
+ +-----------------+
49
+ | Type | Color |
50
+ +-----------------+
51
+ | roses | red |
52
+ | violets | blue |
53
+ +-----------------+
54
+
55
+ ## Sorting
56
+
57
+ Rose.make(:with_sort_by_age_descending, :class => Flower) {
58
+ rows do
59
+ column(:type => "Type")
60
+ column(:color => "Color")
61
+ column(:age => "Age")
62
+ end
63
+ sort("Age", :descending)
64
+ }
65
+
66
+ ## Filtering
67
+
68
+ Rose.make(:with_filter, :class => Flower) {
69
+ rows do
70
+ column(:type => "Type")
71
+ column(:color => "Color")
72
+ column(:age => "Age")
73
+ end
74
+ filter do |row|
75
+ row["Color"] != "blue"
76
+ end
77
+ }
78
+
79
+ ## Summarizing
80
+
81
+ Rose.make(:with_summary, :class => Flower) {
82
+ rows do
83
+ column(:type => "Type")
84
+ column(:color => "Color")
85
+ end
86
+ summary("Type") do
87
+ column("Color") { |colors| colors.uniq.join(", ") }
88
+ column("Count") { |colors| colors.size }
89
+ end
90
+ }
91
+
92
+ ## Pivoting
93
+
94
+ Rose.make(:with_pivot, :class => Flower) {
95
+ rows do
96
+ column(:type => "Type")
97
+ column(:color => "Color")
98
+ column(:age => "Age")
99
+ end
100
+ pivot("Color", "Type") do |rows|
101
+ rows.map(&:Age).map(&:to_i).inject(0) { |sum,x| sum+x }
102
+ end
103
+ }
104
+
105
+ ## Importing
106
+
107
+ Rose.make(:with_find_and_update) do
108
+ rows do
109
+ identity(:id => "ID")
110
+ column(:type => "Type")
111
+ column(:color => "Color")
112
+ column(:age => "Age")
113
+ end
114
+ roots do
115
+ # find is optional. By default will return items with item["ID"] == idy
116
+ find do |items, idy|
117
+ items.find { |item| item.id.to_s == idy }
118
+ end
119
+ update do |item, updates|
120
+ item.color = updates["Color"]
121
+ end
122
+ end
123
+ end
124
+
125
+ `#identity` must be used for one column. Without it `Rose` won't be able to identify which items to update.
126
+
127
+ ### Manually
128
+
129
+ Rose(:with_find_and_update).photosynthesize(@flowers, {
130
+ :updates => {
131
+ "0" => { "Color" => "blue" }
132
+ # ID => Updates
133
+ }
134
+ })
135
+
136
+ ### CSV
137
+
138
+ Rose(:with_find_and_update).photosynthesize(@flowers, {
139
+ :csv_file => "change_flowers.csv"
140
+ })
141
+
142
+ ### Preview
143
+
144
+ Rose.make(:with_preview) do
145
+ rows do
146
+ identity(:id => "ID")
147
+ column(:type => "Type")
148
+ column(:color => "Color")
149
+ column(:age => "Age")
150
+ end
151
+ roots do
152
+ preview_update do |item, updates|
153
+ item.preview(true); item.color = updates["Color"]
154
+ end
155
+ update { raise Exception, "you shouldn't be calling me" }
156
+ end
157
+ end
158
+
159
+ Rose(:with_preview).photosynthesize(@flowers, {
160
+ :updates => {
161
+ "0" => { "Color" => "blue" }
162
+ },
163
+ :preview => true
164
+ })
165
+
166
+ Rose(:with_preview).photosynthesize(@flowers, {
167
+ :csv_file => "change_flowers.csv",
168
+ :preview => true
169
+ })
170
+
171
+ # ActiveRecord
172
+
173
+ First, use the ActiveRecord adapter:
174
+
175
+ config.gem 'rose', :lib => 'rose/active_record'
176
+
177
+ For the most part, the ActiveRecord adapter has the same interface as the ObjectAdapter, except for the following differences:
178
+
179
+ ## Making a Report
180
+
181
+ Employee.rose(:department_salaries) do
182
+ rows do
183
+ column("Name") { |e| "#{e.firstname} #{e.lastname}" }
184
+ column("Department") { |e| e.department.name }
185
+ column("Salary") { |e| e.salary }
186
+ end
187
+ summary("Department") do
188
+ column("Salary") { |salaries| salaries.map(&:to_i).sum }
189
+ end
190
+ end
191
+
192
+ ## Running a Report
193
+
194
+ Employee.rose_for(:department_salaries, :conditions => ["salary <> ?", nil])
195
+
196
+ +----------------------+
197
+ | Department | Salary |
198
+ +----------------------+
199
+ | Accounting | 85000 |
200
+ | Admin | 69000 |
201
+ | Sales | 120000 |
202
+ | Engineering | 122000 |
203
+ | IT | 50000 |
204
+ | Graphics | 42000 |
205
+ +----------------------+
206
+
207
+ `Employee#rose_for` is a helper method that blooms on Employee.find(:all, :conditions => ["salary <> ?", nil]). If you still want direct access to your report, you can use `Employee.seedlings(:department_salaries)`
208
+
209
+ ## Importing (with Preview)
210
+
211
+ Post.rose(:for_update) {
212
+ rows do
213
+ identity(:guid => "ID")
214
+ column("Title", &:title)
215
+ column("Comments") { |item| item.comments.size }
216
+ end
217
+
218
+ sort("Comments", :descending)
219
+
220
+ roots do
221
+ find do |items, idy|
222
+ items.find { |item| item.guid == idy }
223
+ end
224
+ preview_create do |idy, updates|
225
+ post = Post.new(:guid => idy)
226
+ post.title = updates["Title"]
227
+ post
228
+ end
229
+ create do |idy, updates|
230
+ post = create_previewer.call(idy, updates)
231
+ post.save!
232
+ post
233
+ end
234
+ preview_update do |record, updates|
235
+ record.title = updates["Title"]
236
+ end
237
+ update do |record, updates|
238
+ record.update_attribute(:title, updates["Title"])
239
+ end
240
+ end
241
+ }
242
+
243
+ Post.root_for(:for_update, {
244
+ :with => {
245
+ "1" => { "Title" => "New Title" }
246
+ },
247
+ :preview => true
248
+ }) # => Returns a table
249
+
250
+ Post.root_for(:for_update, {
251
+ :with => "change_flowers.csv"
252
+ :preview => true
253
+ })
254
+
255
+ *****
256
+
257
+ # Other
258
+
259
+ Inspired by `Machinist` and `factory_girl`
260
+
261
+ *****
262
+
263
+ # Future
264
+
265
+ * Documentation
266
+
267
+ *****
268
+
269
+ # Copyright
270
+
271
+ Copyright (c) 2010 Henry Hsu. See LICENSE for details.
@@ -0,0 +1,85 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "rose"
8
+ gem.summary = %Q{Reporting like a spring rose, rows and rows of it}
9
+ gem.description = %Q{A slick Ruby DSL for reporting.}
10
+ gem.email = "henry@qlane.com"
11
+ gem.homepage = "http://github.com/hsume2/rose"
12
+ gem.authors = ["Henry Hsu"]
13
+ gem.add_dependency "ruport", ">= 1.6.3"
14
+ gem.add_development_dependency "rspec", ">= 1.2.9"
15
+ gem.add_development_dependency "yard", ">= 0"
16
+ gem.add_development_dependency "cucumber", ">= 0"
17
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
18
+ end
19
+ Jeweler::GemcutterTasks.new
20
+ rescue LoadError
21
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
22
+ end
23
+
24
+ require 'spec/rake/spectask'
25
+ Spec::Rake::SpecTask.new(:spec) do |spec|
26
+ spec.libs << 'lib' << 'spec'
27
+ spec.spec_files = FileList['spec/**/*_spec.rb']
28
+ end
29
+
30
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
31
+ spec.libs << 'lib' << 'spec'
32
+ spec.pattern = 'spec/**/*_spec.rb'
33
+ spec.rcov = true
34
+ spec.rcov_opts << %w{--exclude osx\/objc,gems\/,spec\/,features\/}
35
+ end
36
+
37
+ task :spec => :check_dependencies
38
+
39
+ begin
40
+ require 'cucumber/rake/task'
41
+ Cucumber::Rake::Task.new(:features)
42
+
43
+ task :features => :check_dependencies
44
+ rescue LoadError
45
+ task :features do
46
+ abort "Cucumber is not available. In order to run features, you must: sudo gem install cucumber"
47
+ end
48
+ end
49
+
50
+ begin
51
+ require 'reek'
52
+ require 'reek/rake/task'
53
+ Reek::Rake::Task.new do |t|
54
+ t.fail_on_error = true
55
+ t.verbose = false
56
+ t.source_files = 'lib/**/*.rb'
57
+ end
58
+ rescue LoadError
59
+ task :reek do
60
+ abort "Reek is not available. In order to run reek, you must: sudo gem install reek"
61
+ end
62
+ end
63
+
64
+ begin
65
+ require 'roodi'
66
+ require 'roodi_task'
67
+ RoodiTask.new do |t|
68
+ t.verbose = false
69
+ end
70
+ rescue LoadError
71
+ task :roodi do
72
+ abort "Roodi is not available. In order to run roodi, you must: sudo gem install roodi"
73
+ end
74
+ end
75
+
76
+ task :default => :spec
77
+
78
+ begin
79
+ require 'yard'
80
+ YARD::Rake::YardocTask.new
81
+ rescue LoadError
82
+ task :yardoc do
83
+ abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
84
+ end
85
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.5
@@ -0,0 +1,9 @@
1
+ Feature: something something
2
+ In order to something something
3
+ A user something something
4
+ something something something
5
+
6
+ Scenario: something something
7
+ Given inspiration
8
+ When I create a sweet new gem
9
+ Then everyone should see how awesome I am
@@ -0,0 +1,4 @@
1
+ $:.unshift(File.dirname(__FILE__) + '/../../lib')
2
+ require 'rose'
3
+
4
+ require 'spec/expectations'
@@ -0,0 +1,33 @@
1
+ require 'ruport'
2
+
3
+ module Rose
4
+ autoload :Seedling, 'rose/seedling'
5
+ autoload :Shell, 'rose/shell'
6
+ autoload :ObjectAdapter, 'rose/object'
7
+ autoload :ActiveRecordAdapter, 'rose/active_record'
8
+ autoload :CoreExtensions, 'rose/core_extensions'
9
+
10
+ class << self
11
+ # @return [Hash] global hash of all the named seedlings
12
+ attr_accessor :seedlings
13
+ end
14
+
15
+ self.seedlings = {}
16
+
17
+ # The generate Rose DSL builder
18
+ # @param [Symbol] name the name of the Seedling to make
19
+ # @param [Hash] options
20
+ # @option options [Class] :class (nil) Used during by the adapter to enforce items types
21
+ # @return [Rose::Seedling] the newly formed Seedling
22
+ def self.make(name, options={}, &blk)
23
+ instance = Rose::Seedling.new(Rose::ObjectAdapter, options)
24
+ instance.instance_eval(&blk)
25
+ self.seedlings[name] = Shell.new(instance)
26
+ end
27
+ end
28
+
29
+ # @param [Symbol] name the name of the seedling
30
+ # @return [Rose::Seedling] find seedling by name and returns it
31
+ def Rose(name)
32
+ Rose.seedlings[name]
33
+ end