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 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.
@@ -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.
@@ -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,4 @@
1
+ module CommaHeaven
2
+ module Sqler
3
+ end
4
+ 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,6 @@
1
+ module CommaHeaven
2
+ module Sqler
3
+ class BelongsToColumns < AssociationColumns
4
+ end
5
+ end
6
+ 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,6 @@
1
+ module CommaHeaven
2
+ module Sqler
3
+ class HasManyColumns < AssociationColumns
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module CommaHeaven
2
+ module Sqler
3
+ class HasOneColumns < AssociationColumns
4
+ end
5
+ end
6
+ 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
@@ -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
+
@@ -0,0 +1,2 @@
1
+ --color
2
+ --backtrace
@@ -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