rose 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/.autotest +3 -0
- data/.document +5 -0
- data/.gitignore +24 -0
- data/LICENSE +20 -0
- data/README.markdown +271 -0
- data/Rakefile +85 -0
- data/VERSION +1 -0
- data/features/rose.feature +9 -0
- data/features/step_definitions/rose_steps.rb +0 -0
- data/features/support/env.rb +4 -0
- data/lib/rose.rb +33 -0
- data/lib/rose/active_record.rb +56 -0
- data/lib/rose/attribute.rb +93 -0
- data/lib/rose/core_extensions.rb +27 -0
- data/lib/rose/object.rb +130 -0
- data/lib/rose/proxy.rb +114 -0
- data/lib/rose/ruport.rb +45 -0
- data/lib/rose/seedling.rb +75 -0
- data/lib/rose/shell.rb +40 -0
- data/rose.gemspec +86 -0
- data/spec/core_extensions_spec.rb +52 -0
- data/spec/db/schema.rb +32 -0
- data/spec/examples/update_flowers.csv +4 -0
- data/spec/examples/update_posts.csv +5 -0
- data/spec/rose/active_record_spec.rb +434 -0
- data/spec/rose/object_spec.rb +650 -0
- data/spec/rose_spec.rb +36 -0
- data/spec/spec.opts +6 -0
- data/spec/spec_helper.rb +52 -0
- metadata +160 -0
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'rose/proxy'
|
2
|
+
|
3
|
+
module Rose
|
4
|
+
# Defines the Rose DSL
|
5
|
+
class Seedling
|
6
|
+
# @return [Rose::Proxy::Row] the row proxy containing an array of Attributes
|
7
|
+
attr_reader :row
|
8
|
+
|
9
|
+
# @return [Rose::Proxy::Root] the root proxy containing attribute finder and updater
|
10
|
+
attr_reader :root
|
11
|
+
|
12
|
+
# @return [ObjectAdapter] the adapter
|
13
|
+
attr_reader :adapter
|
14
|
+
|
15
|
+
# @return [Hash] the options used by the adapter
|
16
|
+
attr_reader :options
|
17
|
+
|
18
|
+
# @return [Array] the alterations to be applied to the sprouted Seedling (the report)
|
19
|
+
attr_reader :alterations
|
20
|
+
|
21
|
+
# @param [ObjectAdapter] adapter An ObjectAdapter capable of sprouting a Seedling
|
22
|
+
# (running the report)
|
23
|
+
# @param [Hash] options
|
24
|
+
# @option options [Class] :class (nil) Used during by the adapter to enforce items types
|
25
|
+
def initialize(adapter, options={})
|
26
|
+
@adapter = adapter
|
27
|
+
@options = options
|
28
|
+
@alterations = @options[:alterations] = []
|
29
|
+
end
|
30
|
+
|
31
|
+
# @yield Proxy::Row
|
32
|
+
def rows(&blk)
|
33
|
+
proxy = Proxy::Row.new
|
34
|
+
proxy.instance_eval(&blk)
|
35
|
+
@row = proxy
|
36
|
+
end
|
37
|
+
|
38
|
+
# @param [String, Symbol] column_name the column to sort by
|
39
|
+
# @param [:ascending, :descending] order the order to sort by
|
40
|
+
def sort(column_name, order = :ascending, &sort_block)
|
41
|
+
@options[:sort] = Attribute::Sort.new(column_name, order, &sort_block)
|
42
|
+
@alterations << @options[:sort]
|
43
|
+
end
|
44
|
+
|
45
|
+
# @yield Rose::Attribute::Filter
|
46
|
+
def filter(&filter_block)
|
47
|
+
@options[:filter] = Attribute::Filter.new(nil, nil, filter_block)
|
48
|
+
@alterations << @options[:filter]
|
49
|
+
end
|
50
|
+
|
51
|
+
# @param [String, Symbol] column_name the column to group by
|
52
|
+
# @yield Proxy::Summary
|
53
|
+
def summary(column_name, &blk)
|
54
|
+
proxy = Proxy::Summary.new(column_name)
|
55
|
+
proxy.instance_eval(&blk)
|
56
|
+
@options[:summary] = proxy
|
57
|
+
@alterations << @options[:summary]
|
58
|
+
end
|
59
|
+
|
60
|
+
# @param [String, Symbol] group_column the column to use for row data
|
61
|
+
# @param [String, Symbol] pivot_column the column to use for column data
|
62
|
+
# @param [Proc] value_block the block used to evalue the value data
|
63
|
+
def pivot(group_column, pivot_column, &value_block)
|
64
|
+
@options[:pivot] = Attribute::Pivot.new(group_column, pivot_column, value_block)
|
65
|
+
@alterations << @options[:pivot]
|
66
|
+
end
|
67
|
+
|
68
|
+
# @yield Proxy::Root
|
69
|
+
def roots(&blk)
|
70
|
+
proxy = Proxy::Root.new
|
71
|
+
proxy.instance_eval(&blk)
|
72
|
+
@root = proxy
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
data/lib/rose/shell.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
module Rose
|
2
|
+
# This class is a wrapper around Seedling that provides methods
|
3
|
+
# we don't want invoked within the Rose#make DSL.
|
4
|
+
#
|
5
|
+
# A seedling with a shell is still a seedling. Hence, it should
|
6
|
+
# be have as such!
|
7
|
+
class Shell
|
8
|
+
# @return [Rose::Seedling] the current seedling
|
9
|
+
attr_reader :seedling
|
10
|
+
|
11
|
+
def initialize(seedling)
|
12
|
+
@seedling = seedling
|
13
|
+
end
|
14
|
+
|
15
|
+
# Provides bulk exporting
|
16
|
+
# @param [Array] items the items to sprout the seedling with
|
17
|
+
# @return [Ruport::Data::RoseTable] the resulting table
|
18
|
+
def bloom(items=[], options={})
|
19
|
+
@seedling.adapter.sprout(@seedling, @seedling.options.merge(options).merge(
|
20
|
+
:attributes => @seedling.row.attributes,
|
21
|
+
:items => items
|
22
|
+
))
|
23
|
+
end
|
24
|
+
|
25
|
+
# Provides bulk importing
|
26
|
+
# @param [Hash] options
|
27
|
+
# @option options [Hash,String] :with (required) a Hash of identity (id) => attribute pairs, or a String to a CSV file to update the seedling with
|
28
|
+
# @option options [true, false] :preview (false) whether or not to use the previewer
|
29
|
+
def photosynthesize(items=[], options={})
|
30
|
+
@seedling.adapter.osmosis(@seedling, @seedling.options.merge(options).merge(
|
31
|
+
:items => items
|
32
|
+
))
|
33
|
+
end
|
34
|
+
|
35
|
+
# Delegates methods to the current seedling
|
36
|
+
def method_missing(*args, &blk)
|
37
|
+
@seedling.send(*args, &blk)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/rose.gemspec
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{rose}
|
8
|
+
s.version = "0.0.5"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Henry Hsu"]
|
12
|
+
s.date = %q{2010-07-07}
|
13
|
+
s.description = %q{A slick Ruby DSL for reporting.}
|
14
|
+
s.email = %q{henry@qlane.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.markdown"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".autotest",
|
21
|
+
".document",
|
22
|
+
".gitignore",
|
23
|
+
"LICENSE",
|
24
|
+
"README.markdown",
|
25
|
+
"Rakefile",
|
26
|
+
"VERSION",
|
27
|
+
"features/rose.feature",
|
28
|
+
"features/step_definitions/rose_steps.rb",
|
29
|
+
"features/support/env.rb",
|
30
|
+
"lib/rose.rb",
|
31
|
+
"lib/rose/active_record.rb",
|
32
|
+
"lib/rose/attribute.rb",
|
33
|
+
"lib/rose/core_extensions.rb",
|
34
|
+
"lib/rose/object.rb",
|
35
|
+
"lib/rose/proxy.rb",
|
36
|
+
"lib/rose/ruport.rb",
|
37
|
+
"lib/rose/seedling.rb",
|
38
|
+
"lib/rose/shell.rb",
|
39
|
+
"rose.gemspec",
|
40
|
+
"spec/core_extensions_spec.rb",
|
41
|
+
"spec/db/schema.rb",
|
42
|
+
"spec/examples/update_flowers.csv",
|
43
|
+
"spec/examples/update_posts.csv",
|
44
|
+
"spec/rose/active_record_spec.rb",
|
45
|
+
"spec/rose/object_spec.rb",
|
46
|
+
"spec/rose_spec.rb",
|
47
|
+
"spec/spec.opts",
|
48
|
+
"spec/spec_helper.rb"
|
49
|
+
]
|
50
|
+
s.homepage = %q{http://github.com/hsume2/rose}
|
51
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
52
|
+
s.require_paths = ["lib"]
|
53
|
+
s.rubygems_version = %q{1.3.7}
|
54
|
+
s.summary = %q{Reporting like a spring rose, rows and rows of it}
|
55
|
+
s.test_files = [
|
56
|
+
"spec/core_extensions_spec.rb",
|
57
|
+
"spec/db/schema.rb",
|
58
|
+
"spec/rose/active_record_spec.rb",
|
59
|
+
"spec/rose/object_spec.rb",
|
60
|
+
"spec/rose_spec.rb",
|
61
|
+
"spec/spec_helper.rb"
|
62
|
+
]
|
63
|
+
|
64
|
+
if s.respond_to? :specification_version then
|
65
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
66
|
+
s.specification_version = 3
|
67
|
+
|
68
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
69
|
+
s.add_runtime_dependency(%q<ruport>, [">= 1.6.3"])
|
70
|
+
s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
|
71
|
+
s.add_development_dependency(%q<yard>, [">= 0"])
|
72
|
+
s.add_development_dependency(%q<cucumber>, [">= 0"])
|
73
|
+
else
|
74
|
+
s.add_dependency(%q<ruport>, [">= 1.6.3"])
|
75
|
+
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
76
|
+
s.add_dependency(%q<yard>, [">= 0"])
|
77
|
+
s.add_dependency(%q<cucumber>, [">= 0"])
|
78
|
+
end
|
79
|
+
else
|
80
|
+
s.add_dependency(%q<ruport>, [">= 1.6.3"])
|
81
|
+
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
82
|
+
s.add_dependency(%q<yard>, [">= 0"])
|
83
|
+
s.add_dependency(%q<cucumber>, [">= 0"])
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class MyObject
|
4
|
+
include Rose::CoreExtensions
|
5
|
+
end
|
6
|
+
|
7
|
+
describe "CoreExtensions" do
|
8
|
+
before do
|
9
|
+
@o = MyObject.new
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "#require_keys" do
|
13
|
+
it "should raise exception if missing required key" do
|
14
|
+
lambda {
|
15
|
+
@o.require_keys({:name => 'foo'}, :value)
|
16
|
+
}.should raise_error(ArgumentError, "Missing required key(s): value")
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should not raise exception if not missing required key" do
|
20
|
+
lambda {
|
21
|
+
@o.require_keys({:name => 'foo', :value => 'bar'}, :value)
|
22
|
+
}.should_not raise_error
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should raise exception if missing required keys" do
|
26
|
+
lambda {
|
27
|
+
@o.require_keys({}, :name, :value)
|
28
|
+
}.should raise_error(ArgumentError, "Missing required key(s): name, value")
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should not raise exception if not missing required keys" do
|
32
|
+
lambda {
|
33
|
+
@o.require_keys({:name => 'foo', :value => 'bar'}, :name, :value)
|
34
|
+
}.should_not raise_error
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "#required_values" do
|
39
|
+
it "should return values of required keys" do
|
40
|
+
@o.expects(:require_keys).with({:name => 'foo', :value => 'bar'}, :name, :value)
|
41
|
+
foo, bar = @o.required_values({:name => 'foo', :value => 'bar'}, :name, :value)
|
42
|
+
foo.should == 'foo'
|
43
|
+
bar.should == 'bar'
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should return value of required key" do
|
47
|
+
@o.expects(:require_keys).with({:name => 'foo', :value => 'bar'}, :name)
|
48
|
+
foo = @o.required_values({:name => 'foo', :value => 'bar'}, :name)
|
49
|
+
foo.should == 'foo'
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/spec/db/schema.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
ActiveRecord::Schema.define(:version => 0) do
|
2
|
+
create_table :people, :force => true do |t|
|
3
|
+
t.column :name, :string
|
4
|
+
t.column :type, :string
|
5
|
+
t.column :password, :string
|
6
|
+
t.column :admin, :boolean, :default => false
|
7
|
+
end
|
8
|
+
|
9
|
+
create_table :posts, :force => true do |t|
|
10
|
+
t.column :guid, :string
|
11
|
+
t.column :title, :string
|
12
|
+
t.column :body, :text
|
13
|
+
t.column :published, :boolean, :default => true
|
14
|
+
end
|
15
|
+
|
16
|
+
create_table :comments, :force => true do |t|
|
17
|
+
t.column :post_id, :integer
|
18
|
+
t.column :author_id, :integer
|
19
|
+
t.column :body, :text
|
20
|
+
end
|
21
|
+
|
22
|
+
create_table :subjects, :force => true do |t|
|
23
|
+
t.column :name, :string
|
24
|
+
end
|
25
|
+
|
26
|
+
create_table :tests, :force => true do |t|
|
27
|
+
t.column :student_id, :integer
|
28
|
+
t.column :subject_id, :integer
|
29
|
+
t.column :score, :decimal
|
30
|
+
t.timestamps
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,434 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'rose/active_record'
|
3
|
+
|
4
|
+
module RoseActiveRecordSpecs
|
5
|
+
class Person < ActiveRecord::Base
|
6
|
+
attr_protected :password
|
7
|
+
end
|
8
|
+
|
9
|
+
class Admin < Person
|
10
|
+
end
|
11
|
+
|
12
|
+
class Post < ActiveRecord::Base
|
13
|
+
has_many :comments
|
14
|
+
end
|
15
|
+
|
16
|
+
class Comment < ActiveRecord::Base
|
17
|
+
belongs_to :post
|
18
|
+
belongs_to :author, :class_name => "Person"
|
19
|
+
end
|
20
|
+
|
21
|
+
class Subject < ActiveRecord::Base
|
22
|
+
has_many :tests
|
23
|
+
end
|
24
|
+
|
25
|
+
class Test < ActiveRecord::Base
|
26
|
+
belongs_to :subject
|
27
|
+
belongs_to :student, :class_name => "Person"
|
28
|
+
end
|
29
|
+
|
30
|
+
describe Rose, "ActiveRecord adapter" do
|
31
|
+
before(:suite) do
|
32
|
+
# ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/log/test.log")
|
33
|
+
ActiveRecord::Migration.verbose = false
|
34
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
|
35
|
+
load(File.dirname(__FILE__) + "/../db/schema.rb")
|
36
|
+
|
37
|
+
person_1 = Person.create(:name => "Person #1")
|
38
|
+
person_2 = Person.create(:name => "Person #2")
|
39
|
+
|
40
|
+
post_1 = Post.create(:title => "Post #1", :guid => "P1")
|
41
|
+
post_1.comments.create(:author => person_1)
|
42
|
+
post_1.comments.create(:author => person_2)
|
43
|
+
post_2 = Post.create(:title => "Post #2", :guid => "P2")
|
44
|
+
post_2.comments.create(:author => person_2)
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "make report" do
|
48
|
+
before do
|
49
|
+
Post.rose(:post_comments) do
|
50
|
+
rows do
|
51
|
+
column("Post", &:title)
|
52
|
+
column("Comments") { |item| item.comments.size }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
Post.rose(:post_comments_sorted_asc) do
|
57
|
+
rows do
|
58
|
+
column("Post", &:title)
|
59
|
+
column("Comments") { |item| item.comments.size }
|
60
|
+
end
|
61
|
+
sort("Comments", :ascending)
|
62
|
+
end
|
63
|
+
|
64
|
+
Post.rose(:post_comments_sorted_desc) do
|
65
|
+
rows do
|
66
|
+
column("Post", &:title)
|
67
|
+
column("Comments") { |item| item.comments.size }
|
68
|
+
end
|
69
|
+
sort("Comments", :descending)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should report on posts' comments" do
|
74
|
+
Post.rose_for(:post_comments).should match_table <<-eo_table.gsub(%r{^ }, '')
|
75
|
+
+--------------------+
|
76
|
+
| Post | Comments |
|
77
|
+
+--------------------+
|
78
|
+
| Post #1 | 2 |
|
79
|
+
| Post #2 | 1 |
|
80
|
+
+--------------------+
|
81
|
+
eo_table
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should order by comments size" do
|
85
|
+
Post.rose_for(:post_comments_sorted_asc).should match_table <<-eo_table.gsub(%r{^ }, '')
|
86
|
+
+--------------------+
|
87
|
+
| Post | Comments |
|
88
|
+
+--------------------+
|
89
|
+
| Post #2 | 1 |
|
90
|
+
| Post #1 | 2 |
|
91
|
+
+--------------------+
|
92
|
+
eo_table
|
93
|
+
|
94
|
+
Post.rose_for(:post_comments_sorted_desc).should match_table <<-eo_table.gsub(%r{^ }, '')
|
95
|
+
+--------------------+
|
96
|
+
| Post | Comments |
|
97
|
+
+--------------------+
|
98
|
+
| Post #1 | 2 |
|
99
|
+
| Post #2 | 1 |
|
100
|
+
+--------------------+
|
101
|
+
eo_table
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should report only posts with #2" do
|
105
|
+
Post.rose_for(:post_comments, :conditions => ["title like ?", "%#2%"]).should match_table <<-eo_table.gsub(%r{^ }, '')
|
106
|
+
+--------------------+
|
107
|
+
| Post | Comments |
|
108
|
+
+--------------------+
|
109
|
+
| Post #2 | 1 |
|
110
|
+
+--------------------+
|
111
|
+
eo_table
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe "make report with sigma" do
|
116
|
+
before do
|
117
|
+
Comment.rose(:author_comments) do
|
118
|
+
rows do
|
119
|
+
column("Author") { |item| item.author.name }
|
120
|
+
column("Posts") { |item| item.post.title }
|
121
|
+
column("Comments") { |item| true }
|
122
|
+
end
|
123
|
+
summary("Author") do
|
124
|
+
column("Posts") { |posts| posts.join(", ") }
|
125
|
+
column("Comments") { |comments| comments.size }
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
it "should get authors and their comments" do
|
131
|
+
Comment.rose_for(:author_comments).should match_table <<-eo_table.gsub(%r{^ }, '')
|
132
|
+
+-----------------------------------------+
|
133
|
+
| Author | Posts | Comments |
|
134
|
+
+-----------------------------------------+
|
135
|
+
| Person #1 | Post #1 | 1 |
|
136
|
+
| Person #2 | Post #1, Post #2 | 2 |
|
137
|
+
+-----------------------------------------+
|
138
|
+
eo_table
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
describe "make report with pivot" do
|
143
|
+
it "should make report with pivot" do
|
144
|
+
elisa = Person.create(:name => "Elisa")
|
145
|
+
mary = Person.create(:name => "Mary")
|
146
|
+
english = Subject.create(:name => "English")
|
147
|
+
math = Subject.create(:name => "Math")
|
148
|
+
science = Subject.create(:name => "Science")
|
149
|
+
art = Subject.create(:name => "Art")
|
150
|
+
history = Subject.create(:name => "History")
|
151
|
+
french = Subject.create(:name => "French")
|
152
|
+
|
153
|
+
Test.create(:student => elisa, :subject => english, :score => 87, :created_at => Date.parse('January 2010'))
|
154
|
+
Test.create(:student => elisa, :subject => math, :score => 65, :created_at => Date.parse('January 2010'))
|
155
|
+
Test.create(:student => elisa, :subject => science, :score => 58, :created_at => Date.parse('January 2010'))
|
156
|
+
Test.create(:student => elisa, :subject => art, :score => 89, :created_at => Date.parse('January 2010'))
|
157
|
+
Test.create(:student => elisa, :subject => history, :score => 81, :created_at => Date.parse('January 2010'))
|
158
|
+
Test.create(:student => elisa, :subject => french, :score => 62, :created_at => Date.parse('January 2010'))
|
159
|
+
|
160
|
+
Test.create(:student => elisa, :subject => english, :score => 51, :created_at => Date.parse('February 2010'))
|
161
|
+
Test.create(:student => elisa, :subject => math, :score => 72, :created_at => Date.parse('February 2010'))
|
162
|
+
Test.create(:student => elisa, :subject => science, :score => 89, :created_at => Date.parse('February 2010'))
|
163
|
+
Test.create(:student => elisa, :subject => art, :score => 83, :created_at => Date.parse('February 2010'))
|
164
|
+
Test.create(:student => elisa, :subject => history, :score => 84, :created_at => Date.parse('February 2010'))
|
165
|
+
Test.create(:student => elisa, :subject => french, :score => 57, :created_at => Date.parse('February 2010'))
|
166
|
+
|
167
|
+
Test.create(:student => elisa, :subject => english, :score => 41, :created_at => Date.parse('March 2010'))
|
168
|
+
Test.create(:student => elisa, :subject => math, :score => 71, :created_at => Date.parse('March 2010'))
|
169
|
+
Test.create(:student => elisa, :subject => science, :score => 41, :created_at => Date.parse('March 2010'))
|
170
|
+
Test.create(:student => elisa, :subject => art, :score => 92, :created_at => Date.parse('March 2010'))
|
171
|
+
Test.create(:student => elisa, :subject => history, :score => 91, :created_at => Date.parse('March 2010'))
|
172
|
+
Test.create(:student => elisa, :subject => french, :score => 56, :created_at => Date.parse('March 2010'))
|
173
|
+
|
174
|
+
Test.create(:student => mary, :subject => english, :score => 87, :created_at => Date.parse('January 2010'))
|
175
|
+
Test.create(:student => mary, :subject => math, :score => 53, :created_at => Date.parse('January 2010'))
|
176
|
+
Test.create(:student => mary, :subject => science, :score => 35, :created_at => Date.parse('January 2010'))
|
177
|
+
Test.create(:student => mary, :subject => art, :score => 61, :created_at => Date.parse('January 2010'))
|
178
|
+
Test.create(:student => mary, :subject => history, :score => 58, :created_at => Date.parse('January 2010'))
|
179
|
+
Test.create(:student => mary, :subject => french, :score => 92, :created_at => Date.parse('January 2010'))
|
180
|
+
|
181
|
+
Test.create(:student => mary, :subject => english, :score => 68, :created_at => Date.parse('February 2010'))
|
182
|
+
Test.create(:student => mary, :subject => math, :score => 54, :created_at => Date.parse('February 2010'))
|
183
|
+
Test.create(:student => mary, :subject => science, :score => 56, :created_at => Date.parse('February 2010'))
|
184
|
+
Test.create(:student => mary, :subject => art, :score => 59, :created_at => Date.parse('February 2010'))
|
185
|
+
Test.create(:student => mary, :subject => history, :score => 61, :created_at => Date.parse('February 2010'))
|
186
|
+
Test.create(:student => mary, :subject => french, :score => 93, :created_at => Date.parse('February 2010'))
|
187
|
+
|
188
|
+
Test.create(:student => mary, :subject => english, :score => 41, :created_at => Date.parse('March 2010'))
|
189
|
+
Test.create(:student => mary, :subject => math, :score => 35, :created_at => Date.parse('March 2010'))
|
190
|
+
Test.create(:student => mary, :subject => science, :score => 41, :created_at => Date.parse('March 2010'))
|
191
|
+
Test.create(:student => mary, :subject => art, :score => 48, :created_at => Date.parse('March 2010'))
|
192
|
+
Test.create(:student => mary, :subject => history, :score => 67, :created_at => Date.parse('March 2010'))
|
193
|
+
Test.create(:student => mary, :subject => french, :score => 90, :created_at => Date.parse('March 2010'))
|
194
|
+
|
195
|
+
Test.rose(:scores) do
|
196
|
+
rows do
|
197
|
+
column("Month") { |test| I18n.l(test.created_at, :format => :short) }
|
198
|
+
column("Subject") { |test| test.subject.name }
|
199
|
+
column("Student") { |test| test.student.name }
|
200
|
+
column(:score => "Score")
|
201
|
+
end
|
202
|
+
pivot("Month", "Subject") do |matching_rows|
|
203
|
+
matching_rows.map(&:Score).map(&:to_f).sum
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
Test.rose_for(:scores).should match_table <<-eo_table.gsub(%r{^ }, '')
|
208
|
+
+---------------------------------------------------------------------+
|
209
|
+
| Month | Art | Science | English | French | Math | History |
|
210
|
+
+---------------------------------------------------------------------+
|
211
|
+
| 01 Jan 00:00 | 150.0 | 93.0 | 174.0 | 154.0 | 118.0 | 139.0 |
|
212
|
+
| 01 Feb 00:00 | 142.0 | 145.0 | 119.0 | 150.0 | 126.0 | 145.0 |
|
213
|
+
| 01 Mar 00:00 | 140.0 | 82.0 | 82.0 | 146.0 | 106.0 | 158.0 |
|
214
|
+
+---------------------------------------------------------------------+
|
215
|
+
eo_table
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
describe "run report" do
|
220
|
+
before do
|
221
|
+
Comment.rose(:author_comments) do
|
222
|
+
rows do
|
223
|
+
column("Author") { |item| item.author.name }
|
224
|
+
column("Posts") { |item| item.post.title }
|
225
|
+
column("Comments") { |item| item.destroy }
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
it "should run report within transaction" do
|
231
|
+
all_sizes = lambda { [Person.count, Post.count, Comment.count] }
|
232
|
+
|
233
|
+
lambda {
|
234
|
+
Comment.rose_for(:author_comments)
|
235
|
+
}.should_not change(all_sizes, :call)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
describe "import report" do
|
240
|
+
before do
|
241
|
+
Post.rose(:with_update) {
|
242
|
+
rows do
|
243
|
+
identity(:guid => "ID")
|
244
|
+
column("Title", &:title)
|
245
|
+
column("Comments") { |item| item.comments.size }
|
246
|
+
end
|
247
|
+
|
248
|
+
sort("Comments", :descending)
|
249
|
+
|
250
|
+
roots do
|
251
|
+
find do |items, idy|
|
252
|
+
items.find { |item| item.guid == idy }
|
253
|
+
end
|
254
|
+
update do |record, updates|
|
255
|
+
record.update_attribute(:title, updates["Title"])
|
256
|
+
end
|
257
|
+
end
|
258
|
+
}
|
259
|
+
|
260
|
+
@post_3 = Post.create(:title => "Post #3", :guid => "P3")
|
261
|
+
@post_4 = Post.create(:title => "Post #4", :guid => "P4")
|
262
|
+
end
|
263
|
+
|
264
|
+
after do
|
265
|
+
@post_3.destroy; @post_4.destroy
|
266
|
+
end
|
267
|
+
|
268
|
+
it "should update report" do
|
269
|
+
Post.root_for(:with_update, {
|
270
|
+
:with => {
|
271
|
+
"P3" => { "Title" => "Third Post", "something" => "else" },
|
272
|
+
"P4" => { "Title" => "Fourth Post" }
|
273
|
+
}
|
274
|
+
}).should match_table <<-eo_table.gsub(%r{^ }, '')
|
275
|
+
+-----------------------------+
|
276
|
+
| ID | Title | Comments |
|
277
|
+
+-----------------------------+
|
278
|
+
| P1 | Post #1 | 2 |
|
279
|
+
| P2 | Post #2 | 1 |
|
280
|
+
| P4 | Fourth Post | 0 |
|
281
|
+
| P3 | Third Post | 0 |
|
282
|
+
+-----------------------------+
|
283
|
+
eo_table
|
284
|
+
|
285
|
+
Post.all.map(&:title).should == ["Post #1", "Post #2", "Third Post", "Fourth Post"]
|
286
|
+
end
|
287
|
+
|
288
|
+
it "should update report with conditions" do
|
289
|
+
Post.root_for(:with_update, {
|
290
|
+
:with => {
|
291
|
+
"P3" => { "Title" => "Third Post", "something" => "else" },
|
292
|
+
"P4" => { "Title" => "Fourth Post" }
|
293
|
+
}
|
294
|
+
}, { :conditions => ["title like ?", "%#3%"] }).should match_table <<-eo_table.gsub(%r{^ }, '')
|
295
|
+
+----------------------------+
|
296
|
+
| ID | Title | Comments |
|
297
|
+
+----------------------------+
|
298
|
+
| P3 | Third Post | 0 |
|
299
|
+
+----------------------------+
|
300
|
+
eo_table
|
301
|
+
|
302
|
+
Post.all.map(&:title).should == ["Post #1", "Post #2", "Third Post", "Post #4"]
|
303
|
+
end
|
304
|
+
|
305
|
+
it "should update report from CSV" do
|
306
|
+
Post.root_for(:with_update, {
|
307
|
+
:with => "spec/examples/update_posts.csv"
|
308
|
+
}).should match_table <<-eo_table.gsub(%r{^ }, '')
|
309
|
+
+-----------------------------+
|
310
|
+
| ID | Title | Comments |
|
311
|
+
+-----------------------------+
|
312
|
+
| P1 | Post #1 | 2 |
|
313
|
+
| P2 | Post #2 | 1 |
|
314
|
+
| P4 | Fourth Post | 0 |
|
315
|
+
| P3 | Third Post | 0 |
|
316
|
+
+-----------------------------+
|
317
|
+
eo_table
|
318
|
+
|
319
|
+
Post.all.map(&:title).should == ["Post #1", "Post #2", "Third Post", "Fourth Post"]
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
describe "preview import report" do
|
324
|
+
before do
|
325
|
+
Post.rose(:for_import) {
|
326
|
+
rows do
|
327
|
+
identity(:guid => "ID")
|
328
|
+
column("Title", &:title)
|
329
|
+
column("Comments") { |item| item.comments.size }
|
330
|
+
end
|
331
|
+
|
332
|
+
sort("Comments", :descending)
|
333
|
+
|
334
|
+
roots do
|
335
|
+
find do |items, idy|
|
336
|
+
items.find { |item| item.guid == idy }
|
337
|
+
end
|
338
|
+
preview_update do |record, updates|
|
339
|
+
record.title = updates["Title"]
|
340
|
+
end
|
341
|
+
update { raise Exception, "not me!" }
|
342
|
+
end
|
343
|
+
}
|
344
|
+
end
|
345
|
+
|
346
|
+
it "should preview changes" do
|
347
|
+
Post.root_for(:for_import, {
|
348
|
+
:with => {
|
349
|
+
"P1" => { "Title" => "First Post", "something" => "else" },
|
350
|
+
"P2" => { "Title" => "Second Post" }
|
351
|
+
},
|
352
|
+
:preview => true
|
353
|
+
}).should match_table <<-eo_table.gsub(%r{^ }, '')
|
354
|
+
+-----------------------------+
|
355
|
+
| ID | Title | Comments |
|
356
|
+
+-----------------------------+
|
357
|
+
| P1 | First Post | 2 |
|
358
|
+
| P2 | Second Post | 1 |
|
359
|
+
+-----------------------------+
|
360
|
+
eo_table
|
361
|
+
|
362
|
+
Post.all.map(&:title).should == ["Post #1", "Post #2"]
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
describe "create import report" do
|
367
|
+
before do
|
368
|
+
Post.rose(:for_create) {
|
369
|
+
rows do
|
370
|
+
identity(:guid => "ID")
|
371
|
+
column("Title", &:title)
|
372
|
+
column("Comments") { |item| item.comments.size }
|
373
|
+
end
|
374
|
+
|
375
|
+
sort("Comments", :descending)
|
376
|
+
|
377
|
+
roots do
|
378
|
+
find do |items, idy|
|
379
|
+
items.find { |item| item.guid == idy }
|
380
|
+
end
|
381
|
+
preview_create do |idy, updates|
|
382
|
+
post = Post.new(:guid => idy)
|
383
|
+
post.title = updates["Title"]
|
384
|
+
post
|
385
|
+
end
|
386
|
+
create do |idy, updates|
|
387
|
+
post = create_previewer.call(idy, updates)
|
388
|
+
post.save!
|
389
|
+
post
|
390
|
+
end
|
391
|
+
end
|
392
|
+
}
|
393
|
+
end
|
394
|
+
|
395
|
+
it "should preview create" do
|
396
|
+
Post.root_for(:for_create, {
|
397
|
+
:with => {
|
398
|
+
"P3" => { "Title" => "Post #3" }
|
399
|
+
},
|
400
|
+
:preview => true
|
401
|
+
}).should match_table <<-eo_table.gsub(%r{^ }, '')
|
402
|
+
+-------------------------+
|
403
|
+
| ID | Title | Comments |
|
404
|
+
+-------------------------+
|
405
|
+
| P1 | Post #1 | 2 |
|
406
|
+
| P2 | Post #2 | 1 |
|
407
|
+
| P3 | Post #3 | 0 |
|
408
|
+
+-------------------------+
|
409
|
+
eo_table
|
410
|
+
end
|
411
|
+
|
412
|
+
it "should create" do
|
413
|
+
Post.root_for(:for_create, {
|
414
|
+
:with => {
|
415
|
+
"P3" => { "Title" => "Post #3" }
|
416
|
+
}
|
417
|
+
}).should match_table <<-eo_table.gsub(%r{^ }, '')
|
418
|
+
+-------------------------+
|
419
|
+
| ID | Title | Comments |
|
420
|
+
+-------------------------+
|
421
|
+
| P1 | Post #1 | 2 |
|
422
|
+
| P2 | Post #2 | 1 |
|
423
|
+
| P3 | Post #3 | 0 |
|
424
|
+
+-------------------------+
|
425
|
+
eo_table
|
426
|
+
|
427
|
+
Post.last.title.should == "Post #3"
|
428
|
+
|
429
|
+
Post.last.destroy
|
430
|
+
end
|
431
|
+
end
|
432
|
+
end
|
433
|
+
|
434
|
+
end
|