redhillonrails_core 1.0.6 → 1.0.8

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/CHANGELOG CHANGED
@@ -1,3 +1,12 @@
1
+ 1.0.8
2
+ * handle expressions when creating indexes
3
+ * relay on ActiveRecord add_index method when handling legacy API
4
+ 1.0.7
5
+ * support for partial indexes
6
+ 1.0.6
7
+ * Fixed case-sensitive indexes
8
+ 1.0.5
9
+ * added missing quoting
1
10
  1.0.4
2
11
  * Autoloading
3
12
  1.0.3
@@ -1,14 +1,28 @@
1
- NOTE: This plugin was created by harukizaemon(http://github.com/harukizaemon) but is not supported currently by him.
1
+ = Credits
2
+
3
+ This plugin was created by harukizaemon(http://github.com/harukizaemon) but is not supported currently by him.
4
+ I've forked it to make it edge-rails compatible and to introduce new features.
2
5
 
3
6
  = RedHill on Rails Core
4
7
 
5
- RedHill on Rails Core is a plugin that features to support other RedHill on Rails plugins. Those features include:
8
+ RedHill on Rails Core provides bunch of useful database features:
6
9
 
7
10
  * Creating and dropping views;
8
11
  * Creating and removing foreign-keys;
12
+ * Creating partial indexes (postgresql only)
9
13
  * Obtaining indexes directly from a model class; and
10
14
  * Determining when <code>Schema.define()</code> is running.
11
15
 
16
+ === Installation
17
+
18
+ As a gem
19
+
20
+ gem install redhillonrails_core
21
+
22
+ ...or as a plugin
23
+
24
+ script/plugin install http://github.com/mlomnicki/redhillonrails_core.git
25
+
12
26
  === View Support
13
27
 
14
28
  The plugin provides a mechanism for creating and dropping views as well as
@@ -118,11 +132,22 @@ indexes for a given model--<code>ActiveRecord::Base</code>--class. For example:
118
132
 
119
133
  Would return all the indexes for the +invoices+ table.
120
134
 
121
- === Schema Defining
135
+ === Partial Indexes (indexes with conditions)
122
136
 
123
- The plugin also adds a method--<code>defining?()</code>--to
124
- <code>ActiveRecord::Schema</code> to indicate when <code>define()</code> is running. This is necessary
125
- as some migration plugins must change their behaviour accordingly.
137
+ Partial indexes index only a portion of the database. Only PostgreSQL supports this feature.
138
+
139
+ add_index :users, :username, :unique => true, :conditions => {:state => "active"}
140
+
141
+ === Indexing using an arbitrary expression (PostgreSQL only)
142
+
143
+ Create expression-based indexes:
144
+
145
+ add_index :users, [:first_name, :last_name], :expression => 'LOWER(first_name || last_name)'
146
+ add_index :places, :expression => 'sin(lat) * cos(lng)', :name => 'index_places_on_something'
147
+ add_index :documents, :body, :expression => "USING gin (to_tsvector('english', body))"
148
+
149
+ Expression is a pass-through: no quoting, escaping is done on it. Presumably, this expression is
150
+ part of migrations, or at least, code under your control.
126
151
 
127
152
  === Case-insensitive Indexes
128
153
 
@@ -141,14 +166,14 @@ Note also that this ties in well with Rails built-in support for case-insensitiv
141
166
 
142
167
  validates_uniqueness_of :name, :case_sensitive => false
143
168
 
144
- === See Also
169
+ === Schema Defining
170
+
171
+ The plugin also adds a method--<code>defining?()</code>--to
172
+ <code>ActiveRecord::Schema</code> to indicate when <code>define()</code> is running. This is necessary
173
+ as some migration plugins must change their behaviour accordingly.
145
174
 
146
- * Foreign Key Associations (foreign_key_associations)
147
- * Foreign Key Migrations (foreign_key_migrations)
148
- * Row Version Migrations (row_version_migrations)
149
- * Schema Validations (schema_validations)
175
+ === Contributors
150
176
 
151
- === License
177
+ * François Beausoleil - http://github.com/francois
178
+ * Greg Barnett
152
179
 
153
- This plugin is copyright 2006 by RedHill Consulting, Pty. Ltd. and is released
154
- under the MIT license.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.6
1
+ 1.0.8
@@ -26,6 +26,7 @@ module RedHillConsulting::Core::ActiveRecord
26
26
  columns_without_redhillonrails_core
27
27
  cols = columns_hash
28
28
  indexes.each do |index|
29
+ next if index.columns.empty?
29
30
  column_name = index.columns.reverse.detect { |name| name !~ /_id$/ } || index.columns.last
30
31
  column = cols[column_name]
31
32
  column.case_sensitive = index.case_sensitive?
@@ -58,5 +58,10 @@ module RedHillConsulting::Core::ActiveRecord::ConnectionAdapters
58
58
  reverse_foreign_keys(name).each { |foreign_key| remove_foreign_key(foreign_key.table_name, foreign_key.name, options) }
59
59
  drop_table_without_redhillonrails_core(name, options)
60
60
  end
61
+
62
+ def supports_partial_indexes?
63
+ false
64
+ end
65
+
61
66
  end
62
67
  end
@@ -1,11 +1,13 @@
1
1
  module RedHillConsulting::Core::ActiveRecord::ConnectionAdapters
2
- module IndexDefinition
3
- def case_sensitive?
4
- @case_sensitive.nil? ? true : @case_sensitive
5
- end
2
+ module IndexDefinition
3
+ attr_accessor :conditions, :expression
6
4
 
7
- def case_sensitive=(case_sensitive)
8
- @case_sensitive = case_sensitive
9
- end
10
- end
5
+ def case_sensitive?
6
+ @case_sensitive.nil? ? true : @case_sensitive
7
+ end
8
+
9
+ def case_sensitive=(case_sensitive)
10
+ @case_sensitive = case_sensitive
11
+ end
12
+ end
11
13
  end
@@ -15,43 +15,74 @@ module RedHillConsulting::Core::ActiveRecord::ConnectionAdapters
15
15
  end
16
16
 
17
17
  def add_index(table_name, column_name, options = {})
18
+ column_name, options = [], column_name if column_name.is_a?(Hash)
18
19
  column_names = Array(column_name)
19
- index_name = index_name(table_name, :column => column_names)
20
+ if column_names.empty?
21
+ raise ArgumentError, "No columns and :expression missing from options - cannot create index" if options[:expression].blank?
22
+ raise ArgumentError, "Index name not given. Pass :name option" if options[:name].blank?
23
+ end
24
+
25
+ index_type = options[:unique] ? "UNIQUE" : ""
26
+ index_name = options[:name] || index_name(table_name, column_names)
27
+ conditions = options[:conditions]
20
28
 
21
- if Hash === options # legacy support, since this param was a string
22
- index_type = options[:unique] ? "UNIQUE" : ""
23
- index_name = options[:name] || index_name
29
+ if column_names.empty? then
30
+ sql = "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{options[:expression]}"
24
31
  else
25
- index_type = options
26
- end
32
+ quoted_column_names = column_names.map { |e| options[:case_sensitive] == false && e.to_s !~ /_id$/ ? "LOWER(#{quote_column_name(e)})" : quote_column_name(e) }
27
33
 
28
- quoted_column_names = column_names.map { |e| options[:case_sensitive] == false && e.to_s !~ /_id$/ ? "LOWER(#{quote_column_name(e)})" : quote_column_name(e) }
34
+ sql = "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names.join(", ")})"
35
+ sql += " WHERE (#{ ActiveRecord::Base.send(:sanitize_sql, conditions, quote_table_name(table_name)) })" if conditions
36
+ end
37
+ execute sql
38
+ end
29
39
 
30
- execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names.join(", ")})"
40
+ def supports_partial_indexes?
41
+ true
31
42
  end
43
+
44
+ INDEX_CASE_INSENSITIVE_REGEX = /\((.*LOWER\([^:]+(::text)?\).*)\)/i
45
+ INDEX_PARTIAL_REGEX = /\((.*)\)\s+WHERE (.*)$/i
46
+ INDEX_NON_BTREE_REGEX = /((?:gin|gist|hash).*)$/i
32
47
 
33
48
  def indexes_with_redhillonrails_core(table_name, name = nil)
34
49
  indexes = indexes_without_redhillonrails_core(table_name, name)
50
+ # Process indexes containg expressions and partial indexes
51
+ # Ie. consider
35
52
  result = query(<<-SQL, name)
36
53
  SELECT c2.relname, i.indisunique, pg_catalog.pg_get_indexdef(i.indexrelid, 0, true)
37
54
  FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i
38
55
  WHERE c.relname = '#{table_name}'
39
56
  AND c.oid = i.indrelid AND i.indexrelid = c2.oid
40
- AND i.indisprimary = 'f'
41
- AND i.indexprs IS NOT NULL
57
+ AND i.indisprimary = 'f'
58
+ AND (i.indexprs IS NOT NULL OR i.indpred IS NOT NULL)
42
59
  ORDER BY 1
43
60
  SQL
44
61
 
45
- result.each do |row|
46
- if row[2]=~ /\((.*LOWER\([^:]+(::text)?\).*)\)/i
47
- indexes.delete_if { |index| index.name == row[0] }
48
- column_names = $1.split(", ").map do |name|
49
- name = $1 if name =~ /^LOWER\(([^:]+)(::text)?\)$/i
50
- name = $1 if name =~ /^"(.*)"$/
51
- name
52
- end
53
- index = ActiveRecord::ConnectionAdapters::IndexDefinition.new(table_name, row[0], row[1] == "t", column_names)
54
- index.case_sensitive = false
62
+
63
+ # Correctly process complex indexes, ie:
64
+ # CREATE INDEX test_index ON custom_pages USING btree (lower(title::text), created_at) WHERE kind = 1 AND author_id = 3
65
+ result.each do |(index_name, unique, index_def)|
66
+ case_sensitive_match = INDEX_CASE_INSENSITIVE_REGEX.match(index_def)
67
+ partial_index_match = INDEX_PARTIAL_REGEX.match(index_def)
68
+ if case_sensitive_match || partial_index_match
69
+ # column_definitions may be ie. 'LOWER(lower)' or 'login, deleted_at' or LOWER(login), deleted_at
70
+ column_definitions = case_sensitive_match ? case_sensitive_match[1] : partial_index_match[1]
71
+
72
+ indexes.delete_if { |index| index.name == index_name } # prevent duplicated indexes
73
+ column_names = determine_index_column_names(column_definitions)
74
+
75
+ index = ActiveRecord::ConnectionAdapters::IndexDefinition.new(table_name, index_name, unique == "t", column_names)
76
+ index.case_sensitive = !!case_sensitive_match
77
+ # conditions may be ie. active = true AND deleted_at IS NULL.
78
+ index.conditions = partial_index_match[2] if partial_index_match
79
+ indexes << index
80
+
81
+ elsif non_btree_match = INDEX_NON_BTREE_REGEX.match(index_def) then
82
+ indexes.delete_if { |index| index.name == index_name } # prevent duplicated indexes
83
+
84
+ index = ActiveRecord::ConnectionAdapters::IndexDefinition.new(table_name, index_name, false, [])
85
+ index.expression = non_btree_match[1]
55
86
  indexes << index
56
87
  end
57
88
  end
@@ -127,5 +158,16 @@ module RedHillConsulting::Core::ActiveRecord::ConnectionAdapters
127
158
 
128
159
  foreign_keys
129
160
  end
161
+
162
+ # Converts form like: column1, LOWER(column2)
163
+ # to: column1, column2
164
+ def determine_index_column_names(column_definitions)
165
+ column_definitions.split(", ").map do |name|
166
+ name = $1 if name =~ /^LOWER\(([^:]+)(::text)?\)$/i
167
+ name = $1 if name =~ /^"(.*)"$/
168
+ name
169
+ end
170
+ end
171
+
130
172
  end
131
173
  end
@@ -25,9 +25,17 @@ module RedHillConsulting::Core::ActiveRecord
25
25
  def indexes_with_redhillonrails_core(table, stream)
26
26
  indexes = @connection.indexes(table)
27
27
  indexes.each do |index|
28
- stream.print " add_index #{index.table.inspect}, #{index.columns.inspect}, :name => #{index.name.inspect}"
29
- stream.print ", :unique => true" if index.unique
30
- stream.print ", :case_sensitive => false" unless index.case_sensitive?
28
+ if index.expression.blank? then
29
+ stream.print " add_index #{index.table.inspect}, #{index.columns.inspect}, :name => #{index.name.inspect}"
30
+ stream.print ", :unique => true" if index.unique
31
+ stream.print ", :case_sensitive => false" unless index.case_sensitive?
32
+ stream.print ", :conditions => #{index.conditions.inspect}" unless index.conditions.blank?
33
+ else
34
+ stream.print " add_index #{index.table.inspect}"
35
+ stream.print ", :expression => #{index.expression.inspect}"
36
+ stream.print ", :name => #{index.name.inspect}"
37
+ end
38
+
31
39
  stream.puts
32
40
  end
33
41
  stream.puts unless indexes.empty?
@@ -35,5 +35,3 @@ ActiveRecord::ConnectionAdapters::TableDefinition.send(:include, RedHillConsulti
35
35
  ActiveRecord::ConnectionAdapters::Column.send(:include, RedHillConsulting::Core::ActiveRecord::ConnectionAdapters::Column)
36
36
  ActiveRecord::ConnectionAdapters::AbstractAdapter.send(:include, RedHillConsulting::Core::ActiveRecord::ConnectionAdapters::AbstractAdapter)
37
37
  ActiveRecord::ConnectionAdapters::SchemaStatements.send(:include, RedHillConsulting::Core::ActiveRecord::ConnectionAdapters::SchemaStatements)
38
-
39
-
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{redhillonrails_core}
8
- s.version = "1.0.6"
8
+ s.version = "1.0.8"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
- s.authors = ["Micha\305\202 \305\201omnicki"]
12
- s.date = %q{2010-09-24}
11
+ s.authors = ["Michał Łomnicki"]
12
+ s.date = %q{2010-10-19}
13
13
  s.description = %q{RedHill on Rails Core is a plugin that features to support other RedHill on Rails plugins. It creates and drops views and foreign-keys or obtains indexes directly from a model class.}
14
14
  s.email = %q{michal.lomnicki@gmail.com}
15
15
  s.extra_rdoc_files = [
@@ -48,14 +48,14 @@ Gem::Specification.new do |s|
48
48
  s.homepage = %q{http://github.com/mlomnicki/redhillonrails_core}
49
49
  s.rdoc_options = ["--charset=UTF-8"]
50
50
  s.require_paths = ["lib"]
51
- s.rubygems_version = %q{1.3.6}
51
+ s.rubygems_version = %q{1.3.7}
52
52
  s.summary = %q{RedHill on Rails Core is a plugin that features to support other RedHill on Rails plugins}
53
53
 
54
54
  if s.respond_to? :specification_version then
55
55
  current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
56
56
  s.specification_version = 3
57
57
 
58
- if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
58
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
59
59
  else
60
60
  end
61
61
  else
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 1
7
7
  - 0
8
- - 6
9
- version: 1.0.6
8
+ - 8
9
+ version: 1.0.8
10
10
  platform: ruby
11
11
  authors:
12
12
  - "Micha\xC5\x82 \xC5\x81omnicki"
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-09-24 00:00:00 +02:00
17
+ date: 2010-10-19 00:00:00 +02:00
18
18
  default_executable:
19
19
  dependencies: []
20
20
 
@@ -65,6 +65,7 @@ rdoc_options:
65
65
  require_paths:
66
66
  - lib
67
67
  required_ruby_version: !ruby/object:Gem::Requirement
68
+ none: false
68
69
  requirements:
69
70
  - - ">="
70
71
  - !ruby/object:Gem::Version
@@ -72,6 +73,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
72
73
  - 0
73
74
  version: "0"
74
75
  required_rubygems_version: !ruby/object:Gem::Requirement
76
+ none: false
75
77
  requirements:
76
78
  - - ">="
77
79
  - !ruby/object:Gem::Version
@@ -81,7 +83,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
81
83
  requirements: []
82
84
 
83
85
  rubyforge_project:
84
- rubygems_version: 1.3.6
86
+ rubygems_version: 1.3.7
85
87
  signing_key:
86
88
  specification_version: 3
87
89
  summary: RedHill on Rails Core is a plugin that features to support other RedHill on Rails plugins