redhillonrails_core 1.0.6 → 1.0.8

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