comma-heaven 0.3.0
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/LICENSE +20 -0
- data/README.rdoc +87 -0
- data/lib/comma-heaven.rb +14 -0
- data/lib/comma-heaven/active_record/to_comma_heaven.rb +33 -0
- data/lib/comma-heaven/export.rb +55 -0
- data/lib/comma-heaven/sqler.rb +4 -0
- data/lib/comma-heaven/sqler/association_columns.rb +17 -0
- data/lib/comma-heaven/sqler/belongs_to_columns.rb +6 -0
- data/lib/comma-heaven/sqler/column.rb +44 -0
- data/lib/comma-heaven/sqler/columns.rb +99 -0
- data/lib/comma-heaven/sqler/has_many_columns.rb +6 -0
- data/lib/comma-heaven/sqler/has_one_columns.rb +6 -0
- data/spec/active_record/to_comma_heaven_spec.rb +185 -0
- data/spec/export_spec.rb +65 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +99 -0
- data/spec/sqler/belongs_to_association_spec.rb +21 -0
- data/spec/sqler/column_spec.rb +24 -0
- data/spec/sqler/columns_spec.rb +195 -0
- data/spec/sqler/has_many_columns_spec.rb +27 -0
- data/spec/sqler/has_one_association_spec.rb +21 -0
- metadata +152 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Silvano Stralla
|
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.
|
data/README.rdoc
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
= CommaHeaven
|
2
|
+
|
3
|
+
CommaHeaven aims to be to CSV export what Searchlogic is to search.
|
4
|
+
|
5
|
+
== Advice!
|
6
|
+
|
7
|
+
While in production on some project, CommaHeaven is young. Feature additions or bug fixes are welcome.
|
8
|
+
|
9
|
+
== Install & use
|
10
|
+
|
11
|
+
Install the gem:
|
12
|
+
|
13
|
+
sudo gem install comma-heaven
|
14
|
+
|
15
|
+
Now just set it as a dependency in your project and you are ready to go.
|
16
|
+
|
17
|
+
== Export with comma-heaven
|
18
|
+
|
19
|
+
Let me show how CommaHeaven works using an example. We have the following ActiveRecord models:
|
20
|
+
|
21
|
+
# Tree(id: integer, name: string, age: integer, gardener_id: integer)
|
22
|
+
# Leaf(id: integer, tree_id: integer, position: string, size: float, height_from_ground: float)
|
23
|
+
|
24
|
+
class Tree < ActiveRecord::Base
|
25
|
+
has_many :leafs, :dependent => :destroy
|
26
|
+
end
|
27
|
+
|
28
|
+
class Leaf < ActiveRecord::Base
|
29
|
+
belongs_to :tree
|
30
|
+
end
|
31
|
+
|
32
|
+
Tables contain:
|
33
|
+
|
34
|
+
# Trees:
|
35
|
+
+----+-------+-----+-------------+
|
36
|
+
| id | name | age | gardener_id |
|
37
|
+
+----+-------+-----+-------------+
|
38
|
+
| 37 | Olmo | 100 | 33 |
|
39
|
+
| 38 | Ulivo | 150 | 34 |
|
40
|
+
+----+-------+-----+-------------+
|
41
|
+
|
42
|
+
# Leaves:
|
43
|
+
+----+---------+----------+------+--------------------+
|
44
|
+
| id | tree_id | position | size | height_from_ground |
|
45
|
+
+----+---------+----------+------+--------------------+
|
46
|
+
| 81 | 37 | top | | |
|
47
|
+
| 82 | 37 | middle | | |
|
48
|
+
| 83 | 37 | bottom | | |
|
49
|
+
| 84 | 38 | 0 | | 1.0 |
|
50
|
+
| 85 | 38 | 5 | | 2.0 |
|
51
|
+
+----+---------+----------+------+--------------------+
|
52
|
+
|
53
|
+
CommaHeaven let you export CSV using:
|
54
|
+
|
55
|
+
Tree.to_comma_heaven(:export => { "name" => {"1" => {"as" => "", "include" => "1"} },
|
56
|
+
"leafs" => {"2" => {"export" => { "position" => {"3" => {"as" => "", "include" => '1'} },
|
57
|
+
"height_from_ground" => {"4" => {'as' => '', :include => '1'} } }, 'limit' => 2 } } }).to_csv
|
58
|
+
|
59
|
+
What you obtain is:
|
60
|
+
|
61
|
+
tree_name,leaf_0_position,leaf_0_height_from_ground,leaf_1_position,leaf_1_height_from_ground
|
62
|
+
Olmo,top,,middle,
|
63
|
+
Ulivo,0,1.0,5,2.0
|
64
|
+
|
65
|
+
The export hash explains what to export and how.
|
66
|
+
|
67
|
+
== Opinions
|
68
|
+
|
69
|
+
* CSV export is a common request and still hard to do
|
70
|
+
* Export parameters can be passed through an HTML form
|
71
|
+
* Use joins to produce the dataset to export
|
72
|
+
* Relationships are exported by row (see example above)
|
73
|
+
|
74
|
+
== Note on Patches/Pull Requests
|
75
|
+
|
76
|
+
* Fork the project.
|
77
|
+
* Make your feature addition or bug fix.
|
78
|
+
* Add tests for it. This is important so I don't break it in a
|
79
|
+
future version unintentionally.
|
80
|
+
* Commit, do not mess with rakefile, version, or history.
|
81
|
+
(if you want to have your own version, that is fine but
|
82
|
+
bump version in a commit by itself I can ignore when I pull)
|
83
|
+
* Send me a pull request. Bonus points for topic branches.
|
84
|
+
|
85
|
+
== Copyright
|
86
|
+
|
87
|
+
Copyright (c) 2009-2010 Silvano Stralla. See LICENSE for details.
|
data/lib/comma-heaven.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'fastercsv'
|
2
|
+
|
3
|
+
require 'comma-heaven/active_record/to_comma_heaven'
|
4
|
+
require 'comma-heaven/sqler'
|
5
|
+
require 'comma-heaven/sqler/column'
|
6
|
+
require 'comma-heaven/sqler/columns'
|
7
|
+
require 'comma-heaven/sqler/association_columns'
|
8
|
+
require 'comma-heaven/sqler/has_one_columns'
|
9
|
+
require 'comma-heaven/sqler/has_many_columns'
|
10
|
+
require 'comma-heaven/sqler/belongs_to_columns'
|
11
|
+
require 'comma-heaven/export'
|
12
|
+
|
13
|
+
ActiveRecord::Base.send(:extend, CommaHeaven::Export::Implementation)
|
14
|
+
ActiveRecord::Base.send(:extend, CommaHeaven::ActiveRecord::ClassMethods)
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module CommaHeaven
|
2
|
+
module ActiveRecord
|
3
|
+
module ClassMethods
|
4
|
+
def self.extended(base)
|
5
|
+
base.class_eval do
|
6
|
+
base.class_inheritable_accessor :comma_heaven_columns
|
7
|
+
base.comma_heaven_columns = []
|
8
|
+
|
9
|
+
base.class_inheritable_accessor :comma_heaven_associations
|
10
|
+
base.comma_heaven_associations = []
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_comma_heaven(options = {})
|
15
|
+
options.symbolize_keys!
|
16
|
+
options[:limit] = options[:limit].to_i if options[:limit].kind_of?(String)
|
17
|
+
|
18
|
+
returning FasterCSV::Table.new([]) do |table|
|
19
|
+
columns = CommaHeaven::Sqler::Columns.new(self, options[:export])
|
20
|
+
headers = columns.sql_as
|
21
|
+
|
22
|
+
find(:all, :limit => options[:limit], :joins => columns.joins, :select => columns.select).each do |resource|
|
23
|
+
fields = columns.sql_as.inject([]) do |a, f|
|
24
|
+
a << resource.send(f)
|
25
|
+
end
|
26
|
+
|
27
|
+
table << FasterCSV::Row.new(headers, fields)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module CommaHeaven
|
2
|
+
class Export
|
3
|
+
module Implementation
|
4
|
+
def export(options = {})
|
5
|
+
Export.new(self, scope(:find), options)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_accessor :klass, :current_scope, :options, :export, :limit
|
10
|
+
undef :id if respond_to?(:id)
|
11
|
+
|
12
|
+
def initialize(klass, current_scope, options = {})
|
13
|
+
self.klass = klass
|
14
|
+
self.current_scope = current_scope
|
15
|
+
self.options = options || {}
|
16
|
+
|
17
|
+
self.options.symbolize_keys!
|
18
|
+
|
19
|
+
self.export = self.options[:export] || {}
|
20
|
+
|
21
|
+
self.export.symbolize_keys!
|
22
|
+
|
23
|
+
self.limit = self.options[:limit]
|
24
|
+
end
|
25
|
+
|
26
|
+
def save(options = {})
|
27
|
+
all_options = self.options.merge(options)
|
28
|
+
|
29
|
+
csv_options = all_options.slice(*FasterCSV::DEFAULT_OPTIONS.keys)
|
30
|
+
tch_options = all_options.except(*FasterCSV::DEFAULT_OPTIONS.keys) # TCH means To Comma Heaven
|
31
|
+
|
32
|
+
klass.scoped(current_scope).to_comma_heaven(tch_options.symbolize_keys).to_csv(csv_options.symbolize_keys)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
def method_missing(name, *args, &block)
|
37
|
+
case
|
38
|
+
when column_name?(name)
|
39
|
+
return OpenStruct.new(export[name].values.first) rescue OpenStruct.new({})
|
40
|
+
when association_name?(name)
|
41
|
+
return self.class.new(klass.reflect_on_association(name).klass, {}, export[name].values.first) rescue self.class.new(klass.reflect_on_association(name).klass, {}, {})
|
42
|
+
else
|
43
|
+
return super
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def column_name?(value)
|
48
|
+
klass.column_names.include?(value.to_s)
|
49
|
+
end
|
50
|
+
|
51
|
+
def association_name?(value)
|
52
|
+
klass.reflect_on_all_associations.map(&:name).include?(value.to_sym)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module CommaHeaven
|
2
|
+
module Sqler
|
3
|
+
class AssociationColumns < Columns
|
4
|
+
attr_accessor :position, :association
|
5
|
+
|
6
|
+
# @parent@ is the parameter for passing a reference to the
|
7
|
+
# containing columns array
|
8
|
+
def initialize(association, export, position, parent, index = nil, options = {})
|
9
|
+
self.parent = parent
|
10
|
+
self.position = position
|
11
|
+
self.association = association
|
12
|
+
self.index = index
|
13
|
+
super(association.klass, export, options)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module CommaHeaven
|
2
|
+
module Sqler
|
3
|
+
class Column < Struct.new(:parent, :position, :attribute, :as)
|
4
|
+
delegate :association, :model, :table, :table_alias, :index, :to => :parent
|
5
|
+
|
6
|
+
def select
|
7
|
+
"#{table_alias}.#{attribute} AS #{quote(sql_as)}"
|
8
|
+
end
|
9
|
+
|
10
|
+
def joins
|
11
|
+
case parent
|
12
|
+
when HasManyColumns
|
13
|
+
<<-EOS
|
14
|
+
LEFT JOIN #{quote(table)} AS #{table_alias}
|
15
|
+
ON #{parent.parent.table_alias}.#{model.primary_key} = #{table_alias}.#{association.primary_key_name}
|
16
|
+
AND #{table_alias}.#{association.klass.primary_key} = (SELECT #{association.klass.primary_key} FROM #{association.quoted_table_name} WHERE #{association.primary_key_name} = #{parent.parent.table_alias}.#{model.primary_key} LIMIT #{index}, 1)
|
17
|
+
EOS
|
18
|
+
when BelongsToColumns
|
19
|
+
<<-EOS
|
20
|
+
LEFT JOIN #{quote(table)} AS #{table_alias}
|
21
|
+
ON #{table_alias}.#{model.primary_key} = #{parent.parent.table_alias}.#{association.primary_key_name}
|
22
|
+
EOS
|
23
|
+
when HasOneColumns
|
24
|
+
<<-EOS
|
25
|
+
LEFT JOIN #{quote(table)} AS #{table_alias}
|
26
|
+
ON #{parent.parent.table_alias}.#{model.primary_key} = #{table_alias}.#{association.primary_key_name}
|
27
|
+
EOS
|
28
|
+
else ''
|
29
|
+
end.gsub(/\n/, '').squeeze(' ').strip
|
30
|
+
end
|
31
|
+
|
32
|
+
def sql_as
|
33
|
+
return as % index if as
|
34
|
+
return [table_alias(:singularize), attribute].compact.join('_')
|
35
|
+
end
|
36
|
+
|
37
|
+
protected
|
38
|
+
def quote(string)
|
39
|
+
::ActiveRecord::Base.connection.quote_column_name(string)
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module CommaHeaven
|
2
|
+
module Sqler
|
3
|
+
class Columns < Array
|
4
|
+
attr_accessor :parent, :model, :options, :index
|
5
|
+
|
6
|
+
def initialize(model, export, options = {})
|
7
|
+
@model = model
|
8
|
+
@export = export
|
9
|
+
@options = options
|
10
|
+
|
11
|
+
# Fill the array
|
12
|
+
fill!
|
13
|
+
|
14
|
+
# Sort by position
|
15
|
+
# sort! { |a, b| a.position <=> b.position }
|
16
|
+
end
|
17
|
+
|
18
|
+
def select
|
19
|
+
map(&:select).reject { |e| e.empty? }.join(', ')
|
20
|
+
end
|
21
|
+
|
22
|
+
def joins
|
23
|
+
map(&:joins).compact.uniq.join(" ").gsub(/\n/, '').squeeze(' ')
|
24
|
+
end
|
25
|
+
|
26
|
+
def sql_as
|
27
|
+
map(&:sql_as).flatten
|
28
|
+
end
|
29
|
+
|
30
|
+
def table
|
31
|
+
model.table_name
|
32
|
+
end
|
33
|
+
|
34
|
+
def table_alias(method = :pluralize)
|
35
|
+
return [((parent && parent.parent) ? parent.table_alias(method) : nil), table.send(method), index].compact.join('_')
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
def limit
|
40
|
+
(options[:limit] || 1).to_i
|
41
|
+
end
|
42
|
+
|
43
|
+
def fill!
|
44
|
+
each_exportable_column do |column_or_association, position, index, opts|
|
45
|
+
self << build_column_or_columns(column_or_association, position, index, opts)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def build_column_or_columns(column_or_association, position, index = nil, opts = {})
|
50
|
+
opts = opts.merge(:prefix => self.options[:prefix])
|
51
|
+
opts = opts.merge(:index => index)
|
52
|
+
opts = opts.merge(:on => self.options[:on])
|
53
|
+
|
54
|
+
export = opts.delete(:export)
|
55
|
+
|
56
|
+
case
|
57
|
+
when @model.column_names.include?(column_or_association.to_s)
|
58
|
+
as = opts[:as]
|
59
|
+
return ::CommaHeaven::Sqler::Column.new(self, position, column_or_association.to_s, as.blank? ? nil : as)
|
60
|
+
when association = @model.reflect_on_association(column_or_association.to_sym)
|
61
|
+
klass = "::CommaHeaven::Sqler::#{association.macro.to_s.camelize}Columns".constantize
|
62
|
+
return klass.new(association, export, position, self, index, opts)
|
63
|
+
else
|
64
|
+
raise "Error on #{column_or_association.inspect} on #{model.inspect}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def each_exportable_column
|
69
|
+
@export.to_a.map do |f,o|
|
70
|
+
[f, o.to_a.first.first, o.to_a.first.last]
|
71
|
+
end.sort do |a,b|
|
72
|
+
a[1] <=> b[1]
|
73
|
+
end.each do |column_or_association, position, opts|
|
74
|
+
opts.symbolize_keys!
|
75
|
+
|
76
|
+
unless opts[:include] == '0'
|
77
|
+
association = @model.reflect_on_association(column_or_association.to_sym)
|
78
|
+
if association && association.macro == :has_many
|
79
|
+
|
80
|
+
limit = case opts[:limit]
|
81
|
+
when "" then 1
|
82
|
+
when NilClass then 1
|
83
|
+
when Integer then opts[:limit]
|
84
|
+
when String then opts[:limit].to_i
|
85
|
+
else 0
|
86
|
+
end
|
87
|
+
|
88
|
+
1.upto(limit).each do |index|
|
89
|
+
yield column_or_association, position, index - 1, opts
|
90
|
+
end
|
91
|
+
else
|
92
|
+
yield column_or_association, position, nil, opts
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,185 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe "CommaHeaven" do
|
4
|
+
before(:each) do
|
5
|
+
alice = Gardener.create(:name => 'Alice')
|
6
|
+
bob = Gardener.create(:name => 'Bob')
|
7
|
+
|
8
|
+
olmo = Tree.create(:name => 'Olmo', :age => 100, :gardener => alice)
|
9
|
+
olmo.leafs.create(:position => 'top')
|
10
|
+
olmo.leafs.create(:position => 'middle')
|
11
|
+
olmo.leafs.create(:position => 'bottom')
|
12
|
+
|
13
|
+
ulivo = Tree.create(:name => 'Ulivo', :age => 150, :gardener => bob)
|
14
|
+
ulivo.leafs.create(:position => '0', :height_from_ground => 1)
|
15
|
+
ulivo.leafs.create(:position => '5', :height_from_ground => 2)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'should have options' do
|
19
|
+
Tree.comma_heaven_columns.should be_instance_of(Array)
|
20
|
+
Tree.comma_heaven_associations.should be_instance_of(Array)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should allow export options using string as well as symbol' do
|
24
|
+
expected = <<-EOS
|
25
|
+
tree_name,leaf_0_position,leaf_1_position,leaf_2_position
|
26
|
+
Olmo,top,middle,bottom
|
27
|
+
Ulivo,0,5,
|
28
|
+
EOS
|
29
|
+
|
30
|
+
Tree.to_comma_heaven(:export => {:name => {0 => {:include => '1', :as => ''}}, :age => {1 => {:include => '0', :as => ''}}, :leafs => {2 => {:export => {:position => {4 => {:include => '1', :as => ''}}}, :limit => 3}}}).to_csv.should == expected
|
31
|
+
Tree.to_comma_heaven('export' => {:name => {0 => {:include => '1', :as => ''}}, :age => {1 => {:include => '0', :as => ''}}, :leafs => {2 => {'export' => {:position => {4 => {:include => '1', :as => ''}}}, :limit => 3}}}).to_csv.should == expected
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'should allow limit using a string' do
|
35
|
+
expected = <<-EOS
|
36
|
+
tree_name,leaf_0_position,leaf_1_position,leaf_2_position
|
37
|
+
Olmo,top,middle,bottom
|
38
|
+
Ulivo,0,5,
|
39
|
+
EOS
|
40
|
+
|
41
|
+
Tree.to_comma_heaven(:export => {:name => {0 => {:include => '1', :as => ''}}, :age => {1 => {:include => '0', :as => ''}}, :leafs => {2 => {:export => {:position => {4 => {:include => '1', :as => ''}}}, :limit => 3}}}).to_csv.should == expected
|
42
|
+
Tree.to_comma_heaven(:export => {:name => {0 => {:include => '1', :as => ''}}, :age => {1 => {:include => '0', :as => ''}}, :leafs => {2 => {:export => {:position => {4 => {:include => '1', :as => ''}}}, :limit => '3'}}}).to_csv.should == expected
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should ignore 'as' options if is empty" do
|
46
|
+
Tree.to_comma_heaven(:export => {:name => {0 => {:as => ''}}, :age => {1 => {:as => 'AGE'}}}).to_csv.should == <<-EOS
|
47
|
+
tree_name,AGE
|
48
|
+
Olmo,100
|
49
|
+
Ulivo,150
|
50
|
+
EOS
|
51
|
+
|
52
|
+
Tree.to_comma_heaven(:export => {:name => {0 => {:include => '1', :as => ''}}, :age => {1 => {:include => '0', :as => ''}}, :leafs => {2 => {:export => {:position => {4 => {:include => '1', :as => ''}}}, :limit => 3}}}).to_csv.should == <<-EOS
|
53
|
+
tree_name,leaf_0_position,leaf_1_position,leaf_2_position
|
54
|
+
Olmo,top,middle,bottom
|
55
|
+
Ulivo,0,5,
|
56
|
+
EOS
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should ignore 'limit' options if is empty" do
|
60
|
+
pero = Tree.create(:name => 'Pero', :age => 10)
|
61
|
+
|
62
|
+
Tree.to_comma_heaven(:export => {:name => {0 => {}}, :age => {1 => {}}, :leafs => {2 => {:export => {:position => {4 => {}}}, :limit => ''}}}).to_csv.should == <<-EOS
|
63
|
+
tree_name,tree_age,leaf_0_position
|
64
|
+
Olmo,100,top
|
65
|
+
Ulivo,150,0
|
66
|
+
Pero,10,
|
67
|
+
EOS
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should manage relationships that returns 'nil'" do
|
71
|
+
pero = Tree.create(:name => 'Pero', :age => 10)
|
72
|
+
|
73
|
+
Tree.to_comma_heaven(:export => {:name => {0 => {}}, :gardener => {1 => {:export => {:name => {1 => {}}, :surname => {2 => {}}}}}}).to_csv.should == <<-EOS
|
74
|
+
tree_name,gardener_name,gardener_surname
|
75
|
+
Olmo,Alice,
|
76
|
+
Ulivo,Bob,
|
77
|
+
Pero,,
|
78
|
+
EOS
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should manage 'nil' attributes" do
|
82
|
+
pero = Tree.create(:name => 'Pero')
|
83
|
+
|
84
|
+
Tree.to_comma_heaven(:export => {:name => {0 => {}}, :age => {1 => {}}}).to_csv.should == <<-EOS
|
85
|
+
tree_name,tree_age
|
86
|
+
Olmo,100
|
87
|
+
Ulivo,150
|
88
|
+
Pero,
|
89
|
+
EOS
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'should act correctly when no limit is specified' do
|
93
|
+
pero = Tree.create(:name => 'Pero', :age => 10)
|
94
|
+
|
95
|
+
Tree.to_comma_heaven(:export => {:name => {0 => {}}, :age => {1 => {}}, :leafs => {2 => {:export => {:position => {4 => {}}}}}}).to_csv.should == <<-EOS
|
96
|
+
tree_name,tree_age,leaf_0_position
|
97
|
+
Olmo,100,top
|
98
|
+
Ulivo,150,0
|
99
|
+
Pero,10,
|
100
|
+
EOS
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'should export only selected columns' do
|
104
|
+
Tree.to_comma_heaven(:export => {:name => {0 => {:include => '1'}}, :age => {1 => {:include => '0'}}, :leafs => {2 => {:export => {:position => {4 => {:include => '1'}}}, :limit => 3}}}).to_csv.should == <<-EOS
|
105
|
+
tree_name,leaf_0_position,leaf_1_position,leaf_2_position
|
106
|
+
Olmo,top,middle,bottom
|
107
|
+
Ulivo,0,5,
|
108
|
+
EOS
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should convert to CSV associated resources by column" do
|
112
|
+
Tree.to_comma_heaven(:export => {:name => {0 => {}}, :age => {1 => {}}, :leafs => {2 => {:export => {:position => {4 => {}}}, :limit => 3}}}).to_csv.should == <<-EOS
|
113
|
+
tree_name,tree_age,leaf_0_position,leaf_1_position,leaf_2_position
|
114
|
+
Olmo,100,top,middle,bottom
|
115
|
+
Ulivo,150,0,5,
|
116
|
+
EOS
|
117
|
+
|
118
|
+
Tree.to_comma_heaven(:export => {:name => {0 => {}}, :age => {1 => {}}, :leafs => {2 => {:export => {:position => {4 => {}}}, :limit => 1}}}).to_csv.should == <<-EOS
|
119
|
+
tree_name,tree_age,leaf_0_position
|
120
|
+
Olmo,100,top
|
121
|
+
Ulivo,150,0
|
122
|
+
EOS
|
123
|
+
end
|
124
|
+
|
125
|
+
it "should accept options on what to export" do
|
126
|
+
Tree.to_comma_heaven(:export => {"name" => {"0" => {'include' => '1'}}}).to_csv.should ==
|
127
|
+
"tree_name\nOlmo\nUlivo\n"
|
128
|
+
|
129
|
+
Tree.to_comma_heaven(:export => { "name" => {'0' => {'include' => '1'}},
|
130
|
+
"leafs" => {'1' => {:export => { 'position' => {'2' => {'include' => '1'}},
|
131
|
+
'height_from_ground' => {'3' => {'include' => '1'}}},
|
132
|
+
:limit => 1}}}).to_csv.should ==
|
133
|
+
"tree_name,leaf_0_position,leaf_0_height_from_ground\nOlmo,top,""\nUlivo,0,1.0\n"
|
134
|
+
end
|
135
|
+
|
136
|
+
it "should manage correcly use of limit option" do
|
137
|
+
Tree.to_comma_heaven(:export => {:name => {0 => {}}, :leafs => {1 => {:export => {:position => {2 => {}}}, :limit => 1}}}).to_csv.should ==
|
138
|
+
"tree_name,leaf_0_position\nOlmo,top\nUlivo,0\n"
|
139
|
+
|
140
|
+
Tree.to_comma_heaven(:export => {:name => {0 => {}}, :leafs => {1 => {:export => {:position => {2 => {}}}, :limit => 3}}}).to_csv.should == <<-EOS
|
141
|
+
tree_name,leaf_0_position,leaf_1_position,leaf_2_position
|
142
|
+
Olmo,top,middle,bottom
|
143
|
+
Ulivo,0,5,
|
144
|
+
EOS
|
145
|
+
|
146
|
+
Tree.to_comma_heaven(:export => {:name => {0 => {}}, :leafs => {1 => {:export => {:position => {2 => {}}}, :limit => 5}}}).to_csv.should == <<-EOS
|
147
|
+
tree_name,leaf_0_position,leaf_1_position,leaf_2_position,leaf_3_position,leaf_4_position
|
148
|
+
Olmo,top,middle,bottom,,
|
149
|
+
Ulivo,0,5,,,
|
150
|
+
EOS
|
151
|
+
end
|
152
|
+
|
153
|
+
it "should manage belongs_to association as well as has many" do
|
154
|
+
Tree.to_comma_heaven(:export => {:name => {0 => {}}, :gardener => {1 => {:export => {:name => {1 => {}}, :surname => {2 => {}}}}}}).to_csv.should == <<-EOS
|
155
|
+
tree_name,gardener_name,gardener_surname
|
156
|
+
Olmo,Alice,
|
157
|
+
Ulivo,Bob,
|
158
|
+
EOS
|
159
|
+
|
160
|
+
Tree.to_comma_heaven(:export => {:name => {0 => {}}, :gardener => {1 => {:export => {:name => {2 => {}}, :surname => {3 => {}}}}}, :leafs => {4 => {:export => {:position => {5 => {}}}, :limit => 2}}}).to_csv.should == <<-EOS
|
161
|
+
tree_name,gardener_name,gardener_surname,leaf_0_position,leaf_1_position
|
162
|
+
Olmo,Alice,,top,middle
|
163
|
+
Ulivo,Bob,,0,5
|
164
|
+
EOS
|
165
|
+
end
|
166
|
+
|
167
|
+
it "should manage nested associations" do
|
168
|
+
Leaf.to_comma_heaven(:export => {:position => {0 => {}}, :tree => {1 => {:export => {:name => {2 => {}}, :gardener => {3 => {:export => {:name => {4 => {}}}}}}}}}).to_csv.should == <<-EOS
|
169
|
+
leaf_position,tree_name,tree_gardener_name
|
170
|
+
top,Olmo,Alice
|
171
|
+
middle,Olmo,Alice
|
172
|
+
bottom,Olmo,Alice
|
173
|
+
0,Ulivo,Bob
|
174
|
+
5,Ulivo,Bob
|
175
|
+
EOS
|
176
|
+
end
|
177
|
+
|
178
|
+
it "should allow to rename column on export" do
|
179
|
+
Tree.to_comma_heaven(:export => {:name => {0 => {:as => 'Name'}}, :age => {1 => {:as => 'Age'}}, :leafs => {2 => {:export => {:position => {3 => {:as => 'Position %i'}}}, :limit => 2}}}).to_csv.should == <<-EOS
|
180
|
+
Name,Age,Position 0,Position 1
|
181
|
+
Olmo,100,top,middle
|
182
|
+
Ulivo,150,0,5
|
183
|
+
EOS
|
184
|
+
end
|
185
|
+
end
|
data/spec/export_spec.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "Export" do
|
4
|
+
before(:each) do
|
5
|
+
alice = Gardener.create(:name => 'Alice')
|
6
|
+
bob = Gardener.create(:name => 'Bob')
|
7
|
+
|
8
|
+
olmo = Tree.create(:name => 'Olmo', :age => 100, :gardener => alice)
|
9
|
+
olmo.leafs.create(:position => 'top')
|
10
|
+
olmo.leafs.create(:position => 'middle')
|
11
|
+
olmo.leafs.create(:position => 'bottom')
|
12
|
+
|
13
|
+
ulivo = Tree.create(:name => 'Ulivo', :age => 150, :gardener => bob)
|
14
|
+
ulivo.leafs.create(:position => '0', :height_from_ground => 1)
|
15
|
+
ulivo.leafs.create(:position => '5', :height_from_ground => 2)
|
16
|
+
|
17
|
+
@params =
|
18
|
+
{ 'export' => { "id" => {"0" => {"as" => '', 'include' => '0'} },
|
19
|
+
"name" => {"1" => {"as" => "", "include" => "1"} },
|
20
|
+
"leafs" => {"2" => {"export" => { "position" => {"3" => {"as" => "", "include" => '1'} },
|
21
|
+
"height_from_ground" => {"4" => {'as' => '', :include => '1'} } } } } } }
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should make available columns available as methods' do
|
25
|
+
Tree.export(@params).leafs.should be_instance_of(CommaHeaven::Export)
|
26
|
+
Tree.export(@params).leafs.id.should be_instance_of OpenStruct
|
27
|
+
Tree.export(@params).leafs.position.should == OpenStruct.new("as" => "", "include" => "1")
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'should make associations available as methods' do
|
31
|
+
Tree.export.should be_instance_of(CommaHeaven::Export)
|
32
|
+
Tree.export.name.should == OpenStruct.new
|
33
|
+
|
34
|
+
Tree.export(@params).should be_instance_of(CommaHeaven::Export)
|
35
|
+
Tree.export(@params).id.should be_instance_of OpenStruct
|
36
|
+
Tree.export(@params).name.should == OpenStruct.new("as" => "", "include" => "1")
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should export to CSV" do
|
40
|
+
Tree.export(:export => {:name => {0 => {}}, :age => {1 => {}}}).save.should ==
|
41
|
+
Tree.to_comma_heaven(:export => {:name => {0 => {}}, :age => {1 => {}}}).to_csv
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should export to CSV using customized options made available through FasterCSV" do
|
45
|
+
Tree.export(:export => {:name => {0 => {}}, :age => {1 => {}}}, :col_sep => ";", :force_quotes => true).save.should ==
|
46
|
+
Tree.to_comma_heaven(:export => {:name => {0 => {}}, :age => {1 => {}}}).to_csv(:col_sep => ";", :force_quotes => true)
|
47
|
+
|
48
|
+
Tree.export(:export => {:name => {0 => {}}, :age => {1 => {}}}, 'col_sep' => ";", 'force_quotes' => true).save.should ==
|
49
|
+
Tree.to_comma_heaven(:export => {:name => {0 => {}}, :age => {1 => {}}}).to_csv(:col_sep => ";", :force_quotes => true)
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should respect scopes" do
|
53
|
+
Tree.that_begins_with_o.export(:export => {:name => {0 => {}}, :age => {1 => {}}}).save.should ==
|
54
|
+
Tree.that_begins_with_o.to_comma_heaven(:export => {:name => {0 => {}}, :age => {1 => {}}}).to_csv
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should initialize options to an empty hash" do
|
58
|
+
CommaHeaven::Export.new(Tree, Tree.scoped({})).options.should == Hash.new
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should correctly initialize options" do
|
62
|
+
CommaHeaven::Export.new(Tree, Tree.scoped({}), :col_sep => ';').options.should == {:col_sep => ';'}
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
|
+
require 'rubygems'
|
4
|
+
require 'fastercsv'
|
5
|
+
|
6
|
+
require 'activerecord'
|
7
|
+
|
8
|
+
require 'active_support'
|
9
|
+
require 'actionpack'
|
10
|
+
require 'action_controller'
|
11
|
+
require 'action_view'
|
12
|
+
|
13
|
+
require 'comma-heaven'
|
14
|
+
|
15
|
+
require 'spec'
|
16
|
+
require 'spec/autorun'
|
17
|
+
|
18
|
+
ENV['TZ'] = 'UTC'
|
19
|
+
Time.zone = 'Eastern Time (US & Canada)'
|
20
|
+
|
21
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
|
22
|
+
# ActiveRecord::Base.establish_connection(:adapter => 'mysql', :database => 'comma_heaven_dev', :encoding => 'utf8', :username => 'root', :password => '')
|
23
|
+
ActiveRecord::Base.configurations = true
|
24
|
+
|
25
|
+
ActiveRecord::Schema.verbose = false
|
26
|
+
ActiveRecord::Schema.define(:version => 1) do
|
27
|
+
create_table :gardeners do |t|
|
28
|
+
t.string :name
|
29
|
+
t.string :surname
|
30
|
+
end
|
31
|
+
|
32
|
+
create_table :gardener_clones do |t|
|
33
|
+
t.integer :gardener_id
|
34
|
+
t.string :name
|
35
|
+
t.string :surname
|
36
|
+
end
|
37
|
+
|
38
|
+
create_table :trees do |t|
|
39
|
+
t.string :name
|
40
|
+
t.integer :age
|
41
|
+
t.integer :gardener_id
|
42
|
+
end
|
43
|
+
|
44
|
+
create_table :leafs do |t|
|
45
|
+
t.integer :tree_id
|
46
|
+
t.string :position
|
47
|
+
t.float :size
|
48
|
+
t.float :height_from_ground
|
49
|
+
end
|
50
|
+
|
51
|
+
create_table :cells do |t|
|
52
|
+
t.integer :leaf_id
|
53
|
+
t.float :weight
|
54
|
+
t.float :lat
|
55
|
+
t.float :lng
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
Spec::Runner.configure do |config|
|
60
|
+
config.before(:each) do
|
61
|
+
class Gardener < ActiveRecord::Base
|
62
|
+
has_many :trees
|
63
|
+
has_one :gardener_clone
|
64
|
+
end
|
65
|
+
|
66
|
+
class GardenerClone < ActiveRecord::Base
|
67
|
+
belongs_to :gardener_clone
|
68
|
+
end
|
69
|
+
|
70
|
+
class Tree < ActiveRecord::Base
|
71
|
+
belongs_to :gardener
|
72
|
+
has_many :leafs, :dependent => :destroy
|
73
|
+
|
74
|
+
named_scope :that_begins_with_o, {:conditions => ['name LIKE ?', 'o%']}
|
75
|
+
end
|
76
|
+
|
77
|
+
class Leaf < ActiveRecord::Base
|
78
|
+
belongs_to :tree
|
79
|
+
has_many :cells
|
80
|
+
end
|
81
|
+
|
82
|
+
class Cell < ActiveRecord::Base
|
83
|
+
belongs_to :leaf
|
84
|
+
end
|
85
|
+
|
86
|
+
Gardener.destroy_all
|
87
|
+
GardenerClone.destroy_all
|
88
|
+
Tree.destroy_all
|
89
|
+
Leaf.destroy_all
|
90
|
+
Cell.destroy_all
|
91
|
+
end
|
92
|
+
|
93
|
+
config.after(:each) do
|
94
|
+
Object.send(:remove_const, :Gardener)
|
95
|
+
Object.send(:remove_const, :Tree)
|
96
|
+
Object.send(:remove_const, :Leaf)
|
97
|
+
Object.send(:remove_const, :Cell)
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe "BelongsToColumns" do
|
4
|
+
before(:each) do
|
5
|
+
@leaf = CommaHeaven::Sqler::Columns.new(Leaf, {})
|
6
|
+
@association = Leaf.reflect_on_association(:tree)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should build correct SQL select clause" do
|
10
|
+
column = CommaHeaven::Sqler::BelongsToColumns.new(@association, {:age => {4 => {:include => '1', :as => ''}}}, 1, @leaf)
|
11
|
+
column.select.should == "trees.age AS \"tree_age\""
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should build correct SQL joins clause" do
|
15
|
+
column = CommaHeaven::Sqler::BelongsToColumns.new(@association, {:age => {4 => {:include => '1', :as => ''}}}, 1, @leaf)
|
16
|
+
column.joins.should == <<-EOS.gsub(/\n/, ' ').squeeze(' ').strip
|
17
|
+
LEFT JOIN "trees" AS trees
|
18
|
+
ON trees.id = leafs.tree_id
|
19
|
+
EOS
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe "Column" do
|
4
|
+
before(:each) do
|
5
|
+
@columns = mock(:@columns)
|
6
|
+
@columns.should_receive(:model).any_number_of_times.and_return(Gardener)
|
7
|
+
@columns.should_receive(:table).any_number_of_times.and_return('gardeners')
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should build correct SQL select clause" do
|
11
|
+
@columns.should_receive(:index).any_number_of_times.and_return(nil)
|
12
|
+
@columns.should_receive(:table_alias).with(:singularize).any_number_of_times.and_return('gardener')
|
13
|
+
@columns.should_receive(:table_alias).with().any_number_of_times.and_return('gardeners')
|
14
|
+
column = CommaHeaven::Sqler::Column.new(@columns, 1, 'name', nil)
|
15
|
+
column.select.should == 'gardeners.name AS "gardener_name"'
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should allow exclicit aliasing" do
|
19
|
+
@columns.should_receive(:index).any_number_of_times.and_return(nil)
|
20
|
+
@columns.should_receive(:table_alias).any_number_of_times.and_return('gardeners')
|
21
|
+
column = CommaHeaven::Sqler::Column.new(@columns, 1, 'name', 'as')
|
22
|
+
column.select.should == 'gardeners.name AS "as"'
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,195 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe "Columns" do
|
4
|
+
before(:each) do
|
5
|
+
alice = Gardener.create(:name => 'Alice')
|
6
|
+
bob = Gardener.create(:name => 'Bob')
|
7
|
+
|
8
|
+
olmo = Tree.create(:name => 'Olmo', :age => 100, :gardener => alice)
|
9
|
+
olmo.leafs.create(:position => 'top')
|
10
|
+
olmo.leafs.create(:position => 'middle')
|
11
|
+
olmo.leafs.create(:position => 'bottom')
|
12
|
+
|
13
|
+
ulivo = Tree.create(:name => 'Ulivo', :age => 150, :gardener => bob)
|
14
|
+
ulivo.leafs.create(:position => '0', :height_from_ground => 1)
|
15
|
+
ulivo.leafs.create(:position => '5', :height_from_ground => 2)
|
16
|
+
|
17
|
+
@params =
|
18
|
+
{ 'export' => { "id" => {"0" => {"as" => '', 'include' => '0'} },
|
19
|
+
"name" => {"1" => {"as" => "", "include" => "1"} },
|
20
|
+
"leafs" => {"2" => {"export" => { "position" => {"3" => {"as" => "", "include" => '1'} },
|
21
|
+
"height_from_ground" => {"4" => {'as' => '', :include => '1'} } } } } } }
|
22
|
+
end
|
23
|
+
|
24
|
+
# Examples of SQL to be produced:
|
25
|
+
#
|
26
|
+
# SELECT
|
27
|
+
# c.id,
|
28
|
+
# p0.`address`,
|
29
|
+
# p1.`address`,
|
30
|
+
# p2.`address`,
|
31
|
+
# r0.id,
|
32
|
+
# r0_c.name,
|
33
|
+
# r1.id,
|
34
|
+
# r1_c.name,
|
35
|
+
# r2.id,
|
36
|
+
# r2_c.name,
|
37
|
+
# r3.id,
|
38
|
+
# r3_c.name
|
39
|
+
#
|
40
|
+
# FROM `contacts` c
|
41
|
+
#
|
42
|
+
# LEFT JOIN `registrations` AS r0
|
43
|
+
# ON c.id = r0.`contact_id`
|
44
|
+
# AND r0.id = (SELECT id FROM `registrations` WHERE `contact_id` = c.id ORDER BY `created_at` LIMIT 0, 1)
|
45
|
+
# LEFT JOIN `registrations` AS r1
|
46
|
+
# ON c.id = r1.`contact_id`
|
47
|
+
# AND r1.id = (SELECT id FROM `registrations` WHERE `contact_id` = c.id ORDER BY `created_at` LIMIT 1, 1)
|
48
|
+
# LEFT JOIN `registrations` AS r2
|
49
|
+
# ON c.id = r2.`contact_id`
|
50
|
+
# AND r2.id = (SELECT id FROM `registrations` WHERE `contact_id` = c.id ORDER BY `created_at` LIMIT 2, 1)
|
51
|
+
# LEFT JOIN `registrations` AS r3
|
52
|
+
# ON c.id = r3.`contact_id`
|
53
|
+
# AND r3.id = (SELECT id FROM `registrations` WHERE `contact_id` = c.id ORDER BY `created_at` LIMIT 3, 1)
|
54
|
+
#
|
55
|
+
# LEFT JOIN `courses` AS r0_c
|
56
|
+
# ON r0_c.id = r0.`course_id`
|
57
|
+
# LEFT JOIN `courses` AS r1_c
|
58
|
+
# ON r1_c.id = r1.`course_id`
|
59
|
+
# LEFT JOIN `courses` AS r2_c
|
60
|
+
# ON r2_c.id = r2.`course_id`
|
61
|
+
# LEFT JOIN `courses` AS r3_c
|
62
|
+
# ON r3_c.id = r3.`course_id`
|
63
|
+
#
|
64
|
+
# LEFT JOIN `postal_addresses` AS p0
|
65
|
+
# ON c.id = p0.`contact_id`
|
66
|
+
# AND p0.id = (SELECT id FROM `postal_addresses` WHERE `contact_id` = c.id LIMIT 0, 1)
|
67
|
+
# LEFT JOIN `postal_addresses` AS p1
|
68
|
+
# ON c.id = p1.`contact_id`
|
69
|
+
# AND p1.id = (SELECT id FROM `postal_addresses` WHERE `contact_id` = c.id LIMIT 1, 1)
|
70
|
+
# LEFT JOIN `postal_addresses` AS p2
|
71
|
+
# ON c.id = p2.`contact_id`
|
72
|
+
# AND p2.id = (SELECT id FROM `postal_addresses` WHERE `contact_id` = c.id LIMIT 2, 1)
|
73
|
+
#
|
74
|
+
#
|
75
|
+
#
|
76
|
+
# SELECT p.id, f.id, f.`followup_date`
|
77
|
+
# FROM `adiuvo_patients` p
|
78
|
+
# LEFT JOIN `adiuvo_followup` f ON p.id = f.`patient_id`;
|
79
|
+
#
|
80
|
+
#
|
81
|
+
#
|
82
|
+
# SELECT p.id, f0.id, f0.`followup_date`
|
83
|
+
# FROM `adiuvo_patients` p
|
84
|
+
# LEFT JOIN (SELECT * FROM `adiuvo_followup`) AS f0
|
85
|
+
# ON p.id = f0.`patient_id`
|
86
|
+
# AND f0.id = (SELECT id FROM `adiuvo_followup` WHERE `patient_id` = p.id LIMIT 0, 1);
|
87
|
+
#
|
88
|
+
#
|
89
|
+
#
|
90
|
+
# SELECT p.id, f0.id, f0.`followup_date`, f1.id, f1.`followup_date`
|
91
|
+
# FROM `adiuvo_patients` p
|
92
|
+
# LEFT JOIN (SELECT * FROM `adiuvo_followup`) AS f0
|
93
|
+
# ON p.id = f0.`patient_id`
|
94
|
+
# AND f0.id = (SELECT id FROM `adiuvo_followup` WHERE `patient_id` = p.id LIMIT 0, 1)
|
95
|
+
# LEFT JOIN (SELECT * FROM `adiuvo_followup`) AS f1
|
96
|
+
# ON p.id = f1.`patient_id`
|
97
|
+
# AND f1.id = (SELECT id FROM `adiuvo_followup` WHERE `patient_id` = p.id LIMIT 1, 1);
|
98
|
+
#
|
99
|
+
#
|
100
|
+
#
|
101
|
+
# SELECT p.id, f0.id, f0.`followup_date`, f1.id, f1.`followup_date`, f2.id, f2.`followup_date`
|
102
|
+
# FROM `adiuvo_patients` p
|
103
|
+
# LEFT JOIN (SELECT * FROM `adiuvo_followup`) AS f0
|
104
|
+
# ON p.id = f0.`patient_id`
|
105
|
+
# AND f0.id = (SELECT id FROM `adiuvo_followup` WHERE `patient_id` = p.id LIMIT 0, 1)
|
106
|
+
# LEFT JOIN (SELECT * FROM `adiuvo_followup`) AS f1
|
107
|
+
# ON p.id = f1.`patient_id`
|
108
|
+
# AND f1.id = (SELECT id FROM `adiuvo_followup` WHERE `patient_id` = p.id LIMIT 1, 1)
|
109
|
+
# LEFT JOIN (SELECT * FROM `adiuvo_followup`) AS f2
|
110
|
+
# ON p.id = f2.`patient_id`
|
111
|
+
# AND f2.id = (SELECT id FROM `adiuvo_followup` WHERE `patient_id` = p.id LIMIT 2, 1);
|
112
|
+
#
|
113
|
+
#
|
114
|
+
#
|
115
|
+
# SELECT
|
116
|
+
# CONCAT(
|
117
|
+
# IFNULL(p.id, '""'), ';',
|
118
|
+
# IFNULL(f0.id, '""'), ';',
|
119
|
+
# IFNULL(f0.`followup_date`, '""'), ';',
|
120
|
+
# IFNULL(f1.id, '""'), ';',
|
121
|
+
# IFNULL(f1.`followup_date`, '""'), ';',
|
122
|
+
# IFNULL(f2.id, '""'), ';',
|
123
|
+
# IFNULL(f2.`followup_date`, '""')
|
124
|
+
# )
|
125
|
+
# FROM `adiuvo_patients` p
|
126
|
+
# LEFT JOIN (SELECT * FROM `adiuvo_followup`) AS f0
|
127
|
+
# ON p.id = f0.`patient_id`
|
128
|
+
# AND f0.id = (SELECT id FROM `adiuvo_followup` WHERE `patient_id` = p.id LIMIT 0, 1)
|
129
|
+
# LEFT JOIN (SELECT * FROM `adiuvo_followup`) AS f1
|
130
|
+
# ON p.id = f1.`patient_id`
|
131
|
+
# AND f1.id = (SELECT id FROM `adiuvo_followup` WHERE `patient_id` = p.id LIMIT 1, 1)
|
132
|
+
# LEFT JOIN (SELECT * FROM `adiuvo_followup`) AS f2
|
133
|
+
# ON p.id = f2.`patient_id`
|
134
|
+
# AND f2.id = (SELECT id FROM `adiuvo_followup` WHERE `patient_id` = p.id LIMIT 2, 1);
|
135
|
+
|
136
|
+
|
137
|
+
it "should build correct SQL select clause exporting simple fields" do
|
138
|
+
columns = CommaHeaven::Sqler::Columns.new(Tree, {:name => {0 => {:include => '1', :as => ''}}, :age => {1 => {:include => '1', :as => ''}}})
|
139
|
+
columns.select.should == 'trees.name AS "tree_name", trees.age AS "tree_age"'
|
140
|
+
end
|
141
|
+
|
142
|
+
it "should build correct SQL select clause exporting simple fields, including only wanted fields" do
|
143
|
+
columns = CommaHeaven::Sqler::Columns.new(Tree, {:name => {0 => {:include => '1', :as => ''}}, :age => {1 => {:include => '0', :as => ''}}})
|
144
|
+
columns.select.should == 'trees.name AS "tree_name"'
|
145
|
+
end
|
146
|
+
|
147
|
+
it "should build correct SQL select and joins clauses exporting has many association" do
|
148
|
+
columns = CommaHeaven::Sqler::Columns.new(Tree, {:name => {0 => {:include => '1', :as => ''}}, :age => {1 => {:include => '1', :as => ''}}, :leafs => {2 => {:export => {:position => {4 => {:include => '1', :as => ''}}}, :limit => '3'}}})
|
149
|
+
columns.select.should == 'trees.name AS "tree_name", trees.age AS "tree_age", leafs_0.position AS "leaf_0_position", leafs_1.position AS "leaf_1_position", leafs_2.position AS "leaf_2_position"'
|
150
|
+
columns.joins.should =~ /LEFT JOIN/
|
151
|
+
end
|
152
|
+
|
153
|
+
it "should build correct SQL select and joins clauses exporting belongs to association" do
|
154
|
+
columns = CommaHeaven::Sqler::Columns.new(Tree, {:name => {0 => {:include => '1', :as => ''}}, :age => {1 => {:include => '1', :as => ''}}, :gardener => {2 => {:export => {:surname => {4 => {:include => '1', :as => ''}}}}}})
|
155
|
+
columns.select.should == 'trees.name AS "tree_name", trees.age AS "tree_age", gardeners.surname AS "gardener_surname"'
|
156
|
+
columns.joins.should =~ /\sgardeners\s/
|
157
|
+
columns.joins.should =~ /LEFT JOIN/
|
158
|
+
end
|
159
|
+
|
160
|
+
it "should build corrent SQL select and joins clauses for deeper associations" do
|
161
|
+
columns = CommaHeaven::Sqler::Columns.new(Gardener, {:name => {0 => {:include => '1', :as => ''}}, :trees => {1 => {:export => {:name => {0 => {:include => '1', :as => ''}}, :age => {1 => {:include => '1', :as => ''}}, :gardener => {2 => {:export => {:surname => {4 => {:include => '1', :as => ''}}}}}}, :limit => 3}}})
|
162
|
+
columns.select.should == 'gardeners.name AS "gardener_name", trees_0.name AS "tree_0_name", trees_0.age AS "tree_0_age", trees_0_gardeners.surname AS "tree_0_gardener_surname", trees_1.name AS "tree_1_name", trees_1.age AS "tree_1_age", trees_1_gardeners.surname AS "tree_1_gardener_surname", trees_2.name AS "tree_2_name", trees_2.age AS "tree_2_age", trees_2_gardeners.surname AS "tree_2_gardener_surname"'
|
163
|
+
columns.joins.should =~ /LEFT JOIN/
|
164
|
+
columns.joins.should =~ /trees/
|
165
|
+
columns.joins.should =~ /gardeners/
|
166
|
+
end
|
167
|
+
|
168
|
+
it "should build corrent SQL select and joins clauses for deeper and deeper associations" do
|
169
|
+
export = {
|
170
|
+
:name => {0 => {:include => '1', :as => ''}},
|
171
|
+
:trees => {1 => {:export => {
|
172
|
+
:name => {2 => {:include => '1', :as => ''}},
|
173
|
+
:age => {3 => {:include => '1', :as => ''}},
|
174
|
+
:gardener => {4 => {:export => {
|
175
|
+
:surname => {5 => {:include => '1', :as => ''}}}}},
|
176
|
+
:leafs => {6 => {:export => {
|
177
|
+
:position => {7 => {:include => '1', :as => ''}}}, :limit => 2}}}, :limit => 3}}}
|
178
|
+
|
179
|
+
columns = CommaHeaven::Sqler::Columns.new(Gardener, export)
|
180
|
+
|
181
|
+
# puts "\n\n\n#{columns.joins}\n\n\n"
|
182
|
+
# puts Gardener.scoped(:joins => columns.joins).first.inspect
|
183
|
+
# puts Gardener.scoped(:joins => columns.joins, :select => columns.select).first.instance_variable_get(:"@attributes").inspect
|
184
|
+
# puts Gardener.scoped(:joins => columns.joins, :select => columns.select).all.to_yaml
|
185
|
+
|
186
|
+
columns.joins.should =~ /LEFT JOIN/
|
187
|
+
columns.joins.should =~ /trees/
|
188
|
+
columns.joins.should =~ /gardeners/
|
189
|
+
columns.joins.should =~ /leafs/
|
190
|
+
columns.select.should == 'gardeners.name AS "gardener_name", trees_0.name AS "tree_0_name", trees_0.age AS "tree_0_age", trees_0_gardeners.surname AS "tree_0_gardener_surname", trees_0_leafs_0.position AS "tree_0_leaf_0_position", trees_0_leafs_1.position AS "tree_0_leaf_1_position", trees_1.name AS "tree_1_name", trees_1.age AS "tree_1_age", trees_1_gardeners.surname AS "tree_1_gardener_surname", trees_1_leafs_0.position AS "tree_1_leaf_0_position", trees_1_leafs_1.position AS "tree_1_leaf_1_position", trees_2.name AS "tree_2_name", trees_2.age AS "tree_2_age", trees_2_gardeners.surname AS "tree_2_gardener_surname", trees_2_leafs_0.position AS "tree_2_leaf_0_position", trees_2_leafs_1.position AS "tree_2_leaf_1_position"'
|
191
|
+
|
192
|
+
Gardener.scoped(:joins => columns.joins).count.should == 2
|
193
|
+
Gardener.scoped(:joins => columns.joins, :select => columns.select).first.attributes.to_a.length.should == 16
|
194
|
+
end
|
195
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe "HasManyColumns" do
|
4
|
+
before(:each) do
|
5
|
+
@tree = CommaHeaven::Sqler::Columns.new(Tree, {:name => {0 => {:include => '1', :as => ''}}, :age => {1 => {:include => '1', :as => ''}}})
|
6
|
+
@association = Tree.reflect_on_association(:leafs)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should build correct SQL select clause" do
|
10
|
+
column = CommaHeaven::Sqler::HasManyColumns.new(@association, {:position => {4 => {:include => '1', :as => ''}}}, 1, @tree, 1, :limit => '3')
|
11
|
+
column.select.should == 'leafs_1.position AS "leaf_1_position"'
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should build correct SQL select clause for multiple fields" do
|
15
|
+
column = CommaHeaven::Sqler::HasManyColumns.new(@association, {:position => {4 => {:include => '1', :as => ''}}, :size => {5 => {:include => '1', :as => ''}}}, 1, @tree, 1, :limit => '3')
|
16
|
+
column.select.should == 'leafs_1.position AS "leaf_1_position", leafs_1.size AS "leaf_1_size"'
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should build correct SQL joins clause" do
|
20
|
+
column = CommaHeaven::Sqler::HasManyColumns.new(@association, {:position => {4 => {:include => '1', :as => ''}}}, 1, @tree, 1, :limit => '3')
|
21
|
+
column.joins.should == <<-EOS.gsub(/\n/, ' ').squeeze(' ').strip
|
22
|
+
LEFT JOIN "leafs" AS leafs_1
|
23
|
+
ON trees.id = leafs_1.tree_id
|
24
|
+
AND leafs_1.id = (SELECT id FROM "leafs" WHERE tree_id = trees.id LIMIT 1, 1)
|
25
|
+
EOS
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe "HasOneColumns" do
|
4
|
+
before(:each) do
|
5
|
+
@gardener = CommaHeaven::Sqler::Columns.new(Gardener, {})
|
6
|
+
@association = Gardener.reflect_on_association(:gardener_clone)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should build correct SQL select clause" do
|
10
|
+
column = CommaHeaven::Sqler::HasOneColumns.new(@association, {:name => {4 => {:include => '1', :as => ''}}}, 1, @gardener)
|
11
|
+
column.select.should == "gardener_clones.name AS \"gardener_clone_name\""
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should build correct SQL joins clause" do
|
15
|
+
column = CommaHeaven::Sqler::HasOneColumns.new(@association, {:name => {4 => {:include => '1', :as => ''}}}, 1, @gardener)
|
16
|
+
column.joins.should == <<-EOS.gsub(/\n/, ' ').squeeze(' ').strip
|
17
|
+
LEFT JOIN "gardener_clones" AS gardener_clones
|
18
|
+
ON gardeners.id = gardener_clones.gardener_id
|
19
|
+
EOS
|
20
|
+
end
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: comma-heaven
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 19
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 3
|
9
|
+
- 0
|
10
|
+
version: 0.3.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Silvano Stralla
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-09-10 00:00:00 +02:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: rspec
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 13
|
30
|
+
segments:
|
31
|
+
- 1
|
32
|
+
- 2
|
33
|
+
- 9
|
34
|
+
version: 1.2.9
|
35
|
+
type: :development
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: activerecord
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 3
|
46
|
+
segments:
|
47
|
+
- 0
|
48
|
+
version: "0"
|
49
|
+
type: :runtime
|
50
|
+
version_requirements: *id002
|
51
|
+
- !ruby/object:Gem::Dependency
|
52
|
+
name: actionpack
|
53
|
+
prerelease: false
|
54
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
hash: 3
|
60
|
+
segments:
|
61
|
+
- 0
|
62
|
+
version: "0"
|
63
|
+
type: :runtime
|
64
|
+
version_requirements: *id003
|
65
|
+
- !ruby/object:Gem::Dependency
|
66
|
+
name: fastercsv
|
67
|
+
prerelease: false
|
68
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
hash: 3
|
74
|
+
segments:
|
75
|
+
- 0
|
76
|
+
version: "0"
|
77
|
+
type: :runtime
|
78
|
+
version_requirements: *id004
|
79
|
+
description: CommaHeaven permits easy exports of Rails models to CSV
|
80
|
+
email: silvano.stralla@sistrall.it
|
81
|
+
executables: []
|
82
|
+
|
83
|
+
extensions: []
|
84
|
+
|
85
|
+
extra_rdoc_files:
|
86
|
+
- LICENSE
|
87
|
+
- README.rdoc
|
88
|
+
files:
|
89
|
+
- lib/comma-heaven.rb
|
90
|
+
- lib/comma-heaven/active_record/to_comma_heaven.rb
|
91
|
+
- lib/comma-heaven/export.rb
|
92
|
+
- lib/comma-heaven/sqler.rb
|
93
|
+
- lib/comma-heaven/sqler/association_columns.rb
|
94
|
+
- lib/comma-heaven/sqler/belongs_to_columns.rb
|
95
|
+
- lib/comma-heaven/sqler/column.rb
|
96
|
+
- lib/comma-heaven/sqler/columns.rb
|
97
|
+
- lib/comma-heaven/sqler/has_many_columns.rb
|
98
|
+
- lib/comma-heaven/sqler/has_one_columns.rb
|
99
|
+
- spec/active_record/to_comma_heaven_spec.rb
|
100
|
+
- spec/export_spec.rb
|
101
|
+
- spec/spec.opts
|
102
|
+
- spec/spec_helper.rb
|
103
|
+
- spec/sqler/belongs_to_association_spec.rb
|
104
|
+
- spec/sqler/column_spec.rb
|
105
|
+
- spec/sqler/columns_spec.rb
|
106
|
+
- spec/sqler/has_many_columns_spec.rb
|
107
|
+
- spec/sqler/has_one_association_spec.rb
|
108
|
+
- LICENSE
|
109
|
+
- README.rdoc
|
110
|
+
has_rdoc: true
|
111
|
+
homepage: http://github.com/sistrall/comma-heaven
|
112
|
+
licenses: []
|
113
|
+
|
114
|
+
post_install_message:
|
115
|
+
rdoc_options:
|
116
|
+
- --charset=UTF-8
|
117
|
+
require_paths:
|
118
|
+
- lib
|
119
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
120
|
+
none: false
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
hash: 3
|
125
|
+
segments:
|
126
|
+
- 0
|
127
|
+
version: "0"
|
128
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ">="
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
hash: 3
|
134
|
+
segments:
|
135
|
+
- 0
|
136
|
+
version: "0"
|
137
|
+
requirements: []
|
138
|
+
|
139
|
+
rubyforge_project:
|
140
|
+
rubygems_version: 1.3.7
|
141
|
+
signing_key:
|
142
|
+
specification_version: 3
|
143
|
+
summary: CSV exporter for Rails
|
144
|
+
test_files:
|
145
|
+
- spec/active_record/to_comma_heaven_spec.rb
|
146
|
+
- spec/export_spec.rb
|
147
|
+
- spec/spec_helper.rb
|
148
|
+
- spec/sqler/belongs_to_association_spec.rb
|
149
|
+
- spec/sqler/column_spec.rb
|
150
|
+
- spec/sqler/columns_spec.rb
|
151
|
+
- spec/sqler/has_many_columns_spec.rb
|
152
|
+
- spec/sqler/has_one_association_spec.rb
|