comma-heaven 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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