ghazel-ar-extensions 0.9.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/ChangeLog +145 -0
  2. data/README +169 -0
  3. data/Rakefile +61 -0
  4. data/config/database.yml +7 -0
  5. data/config/database.yml.template +7 -0
  6. data/config/mysql.schema +72 -0
  7. data/config/postgresql.schema +39 -0
  8. data/db/migrate/generic_schema.rb +97 -0
  9. data/db/migrate/mysql_schema.rb +32 -0
  10. data/db/migrate/oracle_schema.rb +5 -0
  11. data/db/migrate/version.rb +4 -0
  12. data/init.rb +31 -0
  13. data/lib/ar-extensions/adapters/abstract_adapter.rb +146 -0
  14. data/lib/ar-extensions/adapters/mysql.rb +10 -0
  15. data/lib/ar-extensions/adapters/oracle.rb +14 -0
  16. data/lib/ar-extensions/adapters/postgresql.rb +9 -0
  17. data/lib/ar-extensions/adapters/sqlite.rb +7 -0
  18. data/lib/ar-extensions/create_and_update/mysql.rb +7 -0
  19. data/lib/ar-extensions/create_and_update.rb +509 -0
  20. data/lib/ar-extensions/csv.rb +309 -0
  21. data/lib/ar-extensions/delete/mysql.rb +3 -0
  22. data/lib/ar-extensions/delete.rb +143 -0
  23. data/lib/ar-extensions/extensions.rb +513 -0
  24. data/lib/ar-extensions/finder_options/mysql.rb +6 -0
  25. data/lib/ar-extensions/finder_options.rb +275 -0
  26. data/lib/ar-extensions/finders.rb +94 -0
  27. data/lib/ar-extensions/foreign_keys.rb +70 -0
  28. data/lib/ar-extensions/fulltext/mysql.rb +44 -0
  29. data/lib/ar-extensions/fulltext.rb +62 -0
  30. data/lib/ar-extensions/import/mysql.rb +50 -0
  31. data/lib/ar-extensions/import/postgresql.rb +0 -0
  32. data/lib/ar-extensions/import/sqlite.rb +22 -0
  33. data/lib/ar-extensions/import.rb +348 -0
  34. data/lib/ar-extensions/insert_select/mysql.rb +7 -0
  35. data/lib/ar-extensions/insert_select.rb +178 -0
  36. data/lib/ar-extensions/synchronize.rb +30 -0
  37. data/lib/ar-extensions/temporary_table/mysql.rb +3 -0
  38. data/lib/ar-extensions/temporary_table.rb +131 -0
  39. data/lib/ar-extensions/union/mysql.rb +6 -0
  40. data/lib/ar-extensions/union.rb +204 -0
  41. data/lib/ar-extensions/util/sql_generation.rb +27 -0
  42. data/lib/ar-extensions/util/support_methods.rb +32 -0
  43. data/lib/ar-extensions/version.rb +9 -0
  44. data/lib/ar-extensions.rb +5 -0
  45. metadata +110 -0
@@ -0,0 +1,131 @@
1
+ module ActiveRecord::Extensions::TemporaryTableSupport # :nodoc:
2
+ def supports_temporary_tables? #:nodoc:
3
+ true
4
+ end
5
+ end
6
+
7
+ class ActiveRecord::Base
8
+ # Returns true if the underlying database connection supports temporary tables
9
+ def self.supports_temporary_tables?
10
+ connection.supports_temporary_tables?
11
+ rescue NoMethodError
12
+ false
13
+ end
14
+
15
+ ######################################################################
16
+ # Creates a temporary table given the passed in options hash. The
17
+ # temporary table is created based off from another table the
18
+ # current model class. This method returns the constant for the new
19
+ # new model. This can also be used with block form (see below).
20
+ #
21
+ # == Parameters
22
+ # * options - the options hash used to define the temporary table.
23
+ #
24
+ # ==== Options
25
+ # <tt>:table_name</tt>::the desired name of the temporary table. If not supplied
26
+ # then a name of "temp_" + the current table_name of the current model
27
+ # will be used.
28
+ # <tt>:like</tt>:: the table model you want to base the temporary tables
29
+ # structure off from. If this is not supplied then the table_name of the
30
+ # current model will be used.
31
+ # <tt>:model_name</tt>:: the name of the model you want to use for the temporary
32
+ # table. This must be compliant with Ruby's naming conventions for
33
+ # constants. If this is not supplied a rails-generated table name will
34
+ # be created which is based off from the table_name of the temporary table.
35
+ # IE: Account.create_temporary_table creates the TempAccount model class
36
+ #
37
+ # ==== Example 1, using defaults
38
+ #
39
+ # class Project < ActiveRecord::Base
40
+ # end
41
+ #
42
+ # > t = Project.create_temporary_table
43
+ # > t.class
44
+ # => "TempProject"
45
+ # > t.superclass
46
+ # => Project
47
+ #
48
+ # This creates a temporary table named 'temp_projects' and creates a constant
49
+ # name TempProject. The table structure is copied from the 'projects' table.
50
+ # TempProject is a subclass of Project as you would expect.
51
+ #
52
+ # ==== Example 2, using <tt>:table_name</tt> and <tt>:model options</tt>
53
+ #
54
+ # Project.create_temporary_table :table_name => 'my_projects', :model => 'MyProject'
55
+ #
56
+ # This creates a temporary table named 'my_projects' and creates a constant named
57
+ # MyProject. The table structure is copied from the 'projects' table.
58
+ #
59
+ # ==== Example 3, using <tt>:like</tt>
60
+ #
61
+ # ActiveRecord::Base.create_temporary_table :like => Project
62
+ #
63
+ # This is the same as calling Project.create_temporary_table.
64
+ #
65
+ # ==== Example 4, using block form
66
+ #
67
+ # Project.create_temporary_table do |t|
68
+ # # ...
69
+ # end
70
+ #
71
+ # Using the block form will automatically drop the temporary table
72
+ # when the block exits. +t+ which is passed into the block is the temporary
73
+ # table class. In the above example +t+ equals TempProject. The block form
74
+ # can be used with all of the available options.
75
+ #
76
+ # === See
77
+ #
78
+ # * +drop+
79
+ #
80
+ ######################################################################
81
+ def self.create_temporary_table(opts={})
82
+ opts[:temporary] ||= !opts[:permanent]
83
+ opts[:like] ||= self
84
+ opts[:table_name] ||= "temp_#{self.table_name}"
85
+ opts[:model_name] ||= ActiveSupport::Inflector.classify(opts[:table_name])
86
+
87
+ if Object.const_defined?(opts[:model_name])
88
+ raise Exception, "Model #{opts[:model_name]} already exists!"
89
+ end
90
+
91
+ like_table_name = opts[:like].table_name || self.table_name
92
+
93
+ connection.execute <<-SQL
94
+ CREATE #{opts[:temporary] ? 'TEMPORARY' : ''} TABLE #{opts[:table_name]}
95
+ LIKE #{like_table_name}
96
+ SQL
97
+
98
+ # Sample evaluation:
99
+ #
100
+ # class ::TempFood < Food
101
+ # set_table_name :temp_food
102
+ #
103
+ # def self.drop
104
+ # connection.execute "DROP TABLE temp_foo"
105
+ # Object.send(:remove_const, self.name.to_sym)
106
+ # end
107
+ # end
108
+ class_eval(<<-RUBY, __FILE__, __LINE__)
109
+ class ::#{opts[:model_name]} < #{self.name}
110
+ set_table_name :#{opts[:table_name]}
111
+
112
+ def self.drop
113
+ connection.execute "DROP TABLE #{opts[:table_name]};"
114
+ Object.send(:remove_const, self.name.to_sym)
115
+ end
116
+ end
117
+ RUBY
118
+
119
+ model = Object.const_get(opts[:model_name])
120
+
121
+ if block_given?
122
+ begin
123
+ yield(model)
124
+ ensure
125
+ model.drop
126
+ end
127
+ else
128
+ return model
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,6 @@
1
+ #insert select functionality is dependent on finder options
2
+ require 'ar-extensions/finder_options/mysql'
3
+
4
+ ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do
5
+ include ActiveRecord::Extensions::Union::UnionSupport
6
+ end
@@ -0,0 +1,204 @@
1
+ module ActiveRecord::Extensions::Union#:nodoc:
2
+ module UnionSupport #:nodoc:
3
+ def supports_union? #:nodoc:
4
+ true
5
+ end
6
+ end
7
+ end
8
+
9
+ class ActiveRecord::Base
10
+ supports_extension :union
11
+
12
+ extend ActiveRecord::Extensions::SqlGeneration
13
+ class << self
14
+ # Find a union of two or more queries
15
+ # === Args
16
+ # Each argument is a hash map of options sent to <tt>:find :all</tt>
17
+ # including <tt>:conditions</tt>, <tt>:join</tt>, <tt>:group</tt>,
18
+ # <tt>:having</tt>, and <tt>:limit</tt>
19
+ #
20
+ # In addition the following options are accepted
21
+ # * <tt>:pre_sql</tt> inserts SQL before the SELECT statement of this protion of the +union+
22
+ # * <tt>:post_sql</tt> appends additional SQL to the end of the statement
23
+ # * <tt>:override_select</tt> is used to override the <tt>SELECT</tt> clause of eager loaded associations
24
+ #
25
+ # == Examples
26
+ # Find the union of a San Fran zipcode with a Seattle zipcode
27
+ # union_args1 = {:conditions => ['zip_id = ?', 94010], :select => :phone_number_id}
28
+ # union_args2 = {:conditions => ['zip_id = ?', 98102], :select => :phone_number_id}
29
+ # Contact.find_union(union_args1, union_args2, ...)
30
+ #
31
+ # SQL> (SELECT phone_number_id FROM contacts WHERE zip_id = 94010) UNION
32
+ # (SELECT phone_number_id FROM contacts WHERE zip_id = 98102) UNION ...
33
+ #
34
+ # == Global Options
35
+ # To specify global options that apply to the entire union, specify a hash as the
36
+ # first parameter with a key <tt>:union_options</tt>. Valid options include
37
+ # <tt>:group</tt>, <tt>:having</tt>, <tt>:order</tt>, and <tt>:limit</tt>
38
+ #
39
+ #
40
+ # Example:
41
+ # Contact.find_union(:union_options => {:limit => 10, :order => 'created_on'},
42
+ # union_args1, union_args2, ...)
43
+ #
44
+ # SQL> ((select phone_number_id from contacts ...) UNION (select phone_number_id from contacts ...)) order by created_on limit 10
45
+ #
46
+ def find_union(*args)
47
+ supports_union!
48
+ find_by_sql(find_union_sql(*args))
49
+ end
50
+
51
+ # Count across a union of two or more queries
52
+ # === Args
53
+ # * +column_name+ - The column to count. Defaults to all ('*')
54
+ # * <tt>*args</tt> - Each additional argument is a hash map of options used by <tt>:find :all</tt>
55
+ # including <tt>:conditions</tt>, <tt>:join</tt>, <tt>:group</tt>,
56
+ # <tt>:having</tt>, and <tt>:limit</tt>
57
+ #
58
+ # In addition the following options are accepted
59
+ # * <tt>:pre_sql</tt> inserts SQL before the SELECT statement of this protion of the +union+
60
+ # * <tt>:post_sql</tt> appends additional SQL to the end of the statement
61
+ # * <tt>:override_select</tt> is used to override the <tt>SELECT</tt> clause of eager loaded associations
62
+ #
63
+ # Note that distinct is implied so a record that matches more than one
64
+ # portion of the union is counted only once.
65
+ #
66
+ # == Global Options
67
+ # To specify global options that apply to the entire union, specify a hash as the
68
+ # first parameter with a key <tt>:union_options</tt>. Valid options include
69
+ # <tt>:group</tt>, <tt>:having</tt>, <tt>:order</tt>, and <tt>:limit</tt>
70
+ #
71
+ # == Examples
72
+ # Count the number of people who live in Seattle and San Francisco
73
+ # Contact.count_union(:phone_number_id,
74
+ # {:conditions => ['zip_id = ?, 94010]'},
75
+ # {:conditions => ['zip_id = ?', 98102]})
76
+ # SQL> select count(*) from ((select phone_number_id from contacts ...) UNION (select phone_number_id from contacts ...)) as counter_tbl;
77
+ def count_union(column_name, *args)
78
+ supports_union!
79
+ count_val = calculate_union(:count, column_name, *args)
80
+ (args.length == 1 && args.first[:limit] && args.first[:limit].to_i < count_val) ? args.first[:limit].to_i : count_val
81
+ end
82
+
83
+ protected
84
+
85
+ #do a union of specified calculation. Only for simple calculations
86
+ def calculate_union(operation, column_name, *args)#:nodoc:
87
+ union_options = remove_union_options(args)
88
+
89
+
90
+ if args.length == 1
91
+ column_name = '*' if column_name == :all
92
+ calculate(operation, column_name, args.first.update(union_options))
93
+
94
+ # For more than one map of options, count off the subquery of all the column_name fields unioned together
95
+ # For example, if column_name is phone_number_id the generated query is
96
+ # Contact.calculate_union(:count, :phone_number_id, args)
97
+ # SQL> select count(*) from
98
+ # ((select phone_number_id from contacts ...)
99
+ # UNION
100
+ # (select phone_number_id from contacts ...)) as counter_tbl
101
+ else
102
+ column_name = primary_key if column_name == :all
103
+ column = column_for column_name
104
+ column_name = "#{table_name}.#{column_name}" unless column_name.to_s.include?('.')
105
+
106
+ group_by = union_options.delete(:group)
107
+ having = union_options.delete(:having)
108
+ query_alias = union_options.delete(:query_alias)||"#{operation}_giraffe"
109
+
110
+
111
+ #aggregate_alias should be table_name_id
112
+ aggregate_alias = column_alias_for('', column_name)
113
+ #main alias is operation_table_name_id
114
+ main_aggregate_alias = column_alias_for(operation, column_name)
115
+
116
+ sql = "SELECT "
117
+ sql << (group_by ? "#{group_by}, #{operation}(#{aggregate_alias})" : "#{operation}(*)")
118
+ sql << " AS #{main_aggregate_alias}"
119
+ sql << " FROM ("
120
+
121
+ #by nature of the union the results will always be distinct, so remove distinct column here
122
+ sql << args.inject([]){|l, a|
123
+ calc = "(#{construct_calculation_sql_with_extension('', column_name, a)})"
124
+ #for group by we need to select the group by column also
125
+ calc.gsub!(" AS #{aggregate_alias}", " AS #{aggregate_alias}, #{group_by} ") if group_by
126
+ l << calc
127
+ }.join(" UNION ")
128
+
129
+ add_union_options!(sql, union_options)
130
+
131
+ sql << ") as #{query_alias}"
132
+
133
+ if group_by
134
+ #add groupings
135
+ sql << " GROUP BY #{group_by}"
136
+ sql << " HAVING #{having}" if having
137
+
138
+ calculated_data = connection.select_all(sql)
139
+
140
+ calculated_data.inject(ActiveSupport::OrderedHash.new) do |all, row|
141
+ key = type_cast_calculated_value(row[group_by], column_for(group_by.to_s))
142
+ value = row[main_aggregate_alias]
143
+ all << [key, type_cast_calculated_value(value, column_for(column), operation)]
144
+ end
145
+
146
+ else
147
+ count_by_sql(sql)
148
+ end
149
+ end
150
+ end
151
+
152
+
153
+ #Add Global Union options
154
+ def add_union_options!(sql, options)#:nodoc:
155
+ sql << " GROUP BY #{options[:group]} " if options[:group]
156
+
157
+ if options[:order] || options[:limit]
158
+ scope = scope(:find)
159
+ add_order!(sql, options[:order], scope)
160
+ add_limit!(sql, options, scope)
161
+ end
162
+ sql
163
+ end
164
+
165
+ #Remove the global union options
166
+ def remove_union_options(args)#:nodoc:
167
+ args.first.is_a?(Hash) && args.first.has_key?(:union_options) ? (args.shift)[:union_options] : {}
168
+ end
169
+
170
+ def construct_calculation_sql_with_extension(operation, column_name, options)
171
+ construct_ar_extension_sql(options.merge(:command => '', :keywords => nil, :distinct => nil)) {|sql, o|
172
+ calc_sql = construct_calculation_sql(operation, column_name, options)
173
+
174
+ #this is really gross but prevents us from rewriting construct_calculation_sql
175
+ calc_sql.gsub!(/^SELECT\s/, "SELECT #{options[:keywords]} ") if options[:keywords]
176
+
177
+ sql << calc_sql
178
+ }
179
+ end
180
+
181
+ # Return the sql for union of the query options specified on the command line
182
+ # If the first parameter is a map containing :union_options, use these
183
+ def find_union_sql(*args)#:nodoc:
184
+ options = remove_union_options(args)
185
+
186
+ if args.length == 1
187
+ return finder_sql_to_string(args.first.update(options))
188
+ end
189
+
190
+ sql = args.inject([]) do |sql_list, union_args|
191
+ part = union_args.merge(:force_eager_load => true,
192
+ :override_select => union_args[:select]||"#{quoted_table_name}.*",
193
+ :select => nil)
194
+ sql_list << "(#{finder_sql_to_string(part)})"
195
+ sql_list
196
+ end.join(" UNION ")
197
+
198
+
199
+ add_union_options!(sql, options)
200
+ sql
201
+ end
202
+ end
203
+ end
204
+
@@ -0,0 +1,27 @@
1
+
2
+ #Extend this module on ActiveRecord to access global functions
3
+ module ActiveRecord
4
+ module Extensions
5
+ module SqlGeneration#:nodoc:
6
+
7
+ protected
8
+
9
+ def post_sql_statements(options)#:nodoc:
10
+ connection.post_sql_statements(quoted_table_name, options).join(' ')
11
+ end
12
+
13
+ def pre_sql_statements(options)#:nodoc:
14
+ connection.pre_sql_statements({:command => 'SELECT'}.merge(options)).join(' ').strip + " "
15
+ end
16
+
17
+ def construct_ar_extension_sql(options={}, valid_options = [], &block)#:nodoc:
18
+ options.assert_valid_keys(valid_options)if valid_options.any?
19
+
20
+ sql = pre_sql_statements(options)
21
+ yield sql, options
22
+ sql << post_sql_statements(options)
23
+ sql
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,32 @@
1
+ #Extend this module on ActiveRecord to access global functions
2
+ class ExtensionNotSupported < Exception; end;
3
+
4
+ module ActiveRecord
5
+ module Extensions
6
+ module SupportMethods#:nodoc:
7
+ def supports_extension(name)
8
+ class_eval(<<-EOS, __FILE__, __LINE__)
9
+ def self.supports_#{name}?#:nodoc:
10
+ connection.supports_#{name}?
11
+ rescue NoMethodError
12
+ false
13
+ end
14
+
15
+ def supports_#{name}?#:nodoc:
16
+ self.class.supports_#{name}?
17
+ end
18
+
19
+ def self.supports_#{name}!#:nodoc:
20
+ supports_#{name}? or raise ExtensionNotSupported.new("#{name} extension is not supported. Please require the adapter file.")
21
+ end
22
+
23
+ def supports_#{name}!#:nodoc:
24
+ self.class.supports_#{name}!
25
+ end
26
+ EOS
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ ActiveRecord::Base.send :extend, ActiveRecord::Extensions::SupportMethods
@@ -0,0 +1,9 @@
1
+
2
+ module ActiveRecord # :nodoc:
3
+ module Extensions # :nodoc:
4
+ module VERSION
5
+ MAJOR, MINOR, REVISION = %W( 0 9 2 )
6
+ STRING = [ MAJOR, MINOR, REVISION ].join( '.' )
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ begin ; require 'rubygems' rescue LoadError; end
2
+ require 'active_record' # ActiveRecord loads the Benchmark library automatically
3
+ require 'active_record/version'
4
+
5
+ require File.expand_path(File.join( File.dirname( __FILE__ ), '..', 'init.rb' ))
metadata ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ghazel-ar-extensions
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.3
5
+ platform: ruby
6
+ authors:
7
+ - Zach Dennis
8
+ - Mark Van Holstyn
9
+ - Blythe Dunham
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+
14
+ date: 2010-10-28 00:00:00 -07:00
15
+ default_executable:
16
+ dependencies:
17
+ - !ruby/object:Gem::Dependency
18
+ name: activerecord
19
+ type: :runtime
20
+ version_requirement:
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ~>
24
+ - !ruby/object:Gem::Version
25
+ version: "2.1"
26
+ version:
27
+ description: Extends ActiveRecord functionality by adding better finder/query support, as well as supporting mass data import, foreign key, CSV and temporary tables
28
+ email: zach.dennis@gmail.com
29
+ executables: []
30
+
31
+ extensions: []
32
+
33
+ extra_rdoc_files:
34
+ - README
35
+ files:
36
+ - init.rb
37
+ - db/migrate/generic_schema.rb
38
+ - db/migrate/mysql_schema.rb
39
+ - db/migrate/oracle_schema.rb
40
+ - db/migrate/version.rb
41
+ - Rakefile
42
+ - ChangeLog
43
+ - README
44
+ - config/database.yml
45
+ - config/database.yml.template
46
+ - config/mysql.schema
47
+ - config/postgresql.schema
48
+ - lib/ar-extensions/adapters/abstract_adapter.rb
49
+ - lib/ar-extensions/adapters/mysql.rb
50
+ - lib/ar-extensions/adapters/oracle.rb
51
+ - lib/ar-extensions/adapters/postgresql.rb
52
+ - lib/ar-extensions/adapters/sqlite.rb
53
+ - lib/ar-extensions/create_and_update/mysql.rb
54
+ - lib/ar-extensions/create_and_update.rb
55
+ - lib/ar-extensions/csv.rb
56
+ - lib/ar-extensions/delete/mysql.rb
57
+ - lib/ar-extensions/delete.rb
58
+ - lib/ar-extensions/extensions.rb
59
+ - lib/ar-extensions/finders.rb
60
+ - lib/ar-extensions/finder_options/mysql.rb
61
+ - lib/ar-extensions/finder_options.rb
62
+ - lib/ar-extensions/foreign_keys.rb
63
+ - lib/ar-extensions/fulltext/mysql.rb
64
+ - lib/ar-extensions/fulltext.rb
65
+ - lib/ar-extensions/import/mysql.rb
66
+ - lib/ar-extensions/import/postgresql.rb
67
+ - lib/ar-extensions/import/sqlite.rb
68
+ - lib/ar-extensions/import.rb
69
+ - lib/ar-extensions/insert_select/mysql.rb
70
+ - lib/ar-extensions/insert_select.rb
71
+ - lib/ar-extensions/synchronize.rb
72
+ - lib/ar-extensions/temporary_table/mysql.rb
73
+ - lib/ar-extensions/temporary_table.rb
74
+ - lib/ar-extensions/union/mysql.rb
75
+ - lib/ar-extensions/union.rb
76
+ - lib/ar-extensions/util/sql_generation.rb
77
+ - lib/ar-extensions/util/support_methods.rb
78
+ - lib/ar-extensions/version.rb
79
+ - lib/ar-extensions.rb
80
+ has_rdoc: true
81
+ homepage: http://www.continuousthinking.com/tags/arext
82
+ licenses: []
83
+
84
+ post_install_message:
85
+ rdoc_options:
86
+ - --main
87
+ - README
88
+ require_paths:
89
+ - lib
90
+ required_ruby_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: "0"
95
+ version:
96
+ required_rubygems_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: "0"
101
+ version:
102
+ requirements: []
103
+
104
+ rubyforge_project: arext
105
+ rubygems_version: 1.3.5
106
+ signing_key:
107
+ specification_version: 3
108
+ summary: Extends ActiveRecord functionality.
109
+ test_files: []
110
+