ar_pg_array 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README +39 -0
- data/Rakefile +42 -0
- data/init.rb +3 -0
- data/install.rb +1 -0
- data/lib/ar_pg_array/querying.rb +88 -0
- data/lib/ar_pg_array/querying_arel.rb +74 -0
- data/lib/ar_pg_array/references_by.rb +71 -0
- data/lib/ar_pg_array/schema.rb +237 -0
- data/lib/ar_pg_array/schema_arel.rb +90 -0
- data/lib/ar_pg_array.rb +11 -0
- data/uninstall.rb +1 -0
- metadata +93 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Sokolov Yura aka funny_falcon
|
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.
|
data/README
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
PostgresArrays
|
2
|
+
==============
|
3
|
+
|
4
|
+
This library adds ability to use PostgreSQL array types with ActiveRecord.
|
5
|
+
|
6
|
+
> User.find(:all, :conditions=>['arr @> ?', [1,2,3].pg])
|
7
|
+
SELECT * FROM "users" WHERE ('arr' @> E'{"1", "2", "3"}')
|
8
|
+
> User.find(:all, :conditions=>['arr @> ?', [1,2,3].pg(:integer)])
|
9
|
+
SELECT * FROM "users" WHERE (arr @> '{1,2,3}')
|
10
|
+
> User.find(:all, :conditions=>['arr @> ?', [1,2,3].pg(:float)])
|
11
|
+
SELECT * FROM "users" WHERE (arr @> '{1.0,2.0,3.0}')
|
12
|
+
> u = User.find(1)
|
13
|
+
SELECT * FROM "users" WHERE ("users"."id" = 1)
|
14
|
+
=> #<User id: 1, ..., arr: [1,2]>
|
15
|
+
> u.arr = [3,4]
|
16
|
+
> u.save
|
17
|
+
UPDATE "users" SET "db_ar" = '{3.0,4.0}' WHERE "id" = 19
|
18
|
+
> User.find(:all, :conditions=>{:arr=>[3,4].pg})
|
19
|
+
SELECT * FROM "users" WHERE ("users"."arr" = E'{"3", "4"}')
|
20
|
+
> User.find(:all, :conditions=>{:arr=>[3,4].search_any(:float)})
|
21
|
+
SELECT * FROM "users" WHERE ("users"."arr" && '{3.0,4.0}')
|
22
|
+
> User.find(:all, :conditions=>{:arr=>[3,4].search_all(:integer)})
|
23
|
+
SELECT * FROM "users" WHERE ("users"."arr" @> '{3,4}')
|
24
|
+
> User.find(:all, :conditions=>{:arr=>[3,4].search_subarray(:safe)})
|
25
|
+
SELECT * FROM "users" WHERE ("users"."arr" <@ '{3,4}')
|
26
|
+
|
27
|
+
class U < ActiveRecord::Migration
|
28
|
+
def self.up
|
29
|
+
create_table :users do |t|
|
30
|
+
t.integer_array :int_ar
|
31
|
+
end
|
32
|
+
add_column :users, :fl_ar, :float_array
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
Plugin worked with rails 2.3.x. integer_array works in ActiveRecord 3 at the moment.
|
37
|
+
|
38
|
+
|
39
|
+
Copyright (c) 2010 Sokolov Yura aka funny_falcon, released under the MIT license
|
data/Rakefile
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
|
5
|
+
desc 'Default: run unit tests.'
|
6
|
+
task :default => :test
|
7
|
+
|
8
|
+
desc 'Test the postgres_arrays plugin.'
|
9
|
+
Rake::TestTask.new(:test) do |t|
|
10
|
+
t.libs << 'lib'
|
11
|
+
t.pattern = 'test/**/*_test.rb'
|
12
|
+
t.verbose = true
|
13
|
+
end
|
14
|
+
|
15
|
+
desc 'Generate documentation for the postgres_arrays plugin.'
|
16
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
17
|
+
rdoc.rdoc_dir = 'rdoc'
|
18
|
+
rdoc.title = 'PostgresArrays'
|
19
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
20
|
+
rdoc.rdoc_files.include('README')
|
21
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
22
|
+
end
|
23
|
+
|
24
|
+
begin
|
25
|
+
require 'jeweler'
|
26
|
+
Jeweler::Tasks.new do |gemspec|
|
27
|
+
gemspec.name = "ar_pg_array"
|
28
|
+
gemspec.summary = "Use power of PostgreSQL Arrays in ActiveRecord"
|
29
|
+
gemspec.description = "ar_pg_array includes support of PostgreSQL's int[], float[], text[], timestamptz[] etc. into ActiveRecord. You could define migrations for array columns, query on array columns."
|
30
|
+
gemspec.email = "funny.falcon@gmail.com"
|
31
|
+
gemspec.homepage = "http://github.com/funny-falcon/activerecord-postgresql-arrays"
|
32
|
+
gemspec.authors = ["Sokolov Yura aka funny_falcon"]
|
33
|
+
gemspec.add_dependency('activerecord', '>=2.3.5')
|
34
|
+
gemspec.rubyforge_project = 'ar_pg_array'
|
35
|
+
end
|
36
|
+
Jeweler::GemcutterTasks.new
|
37
|
+
Jeweler::RubyforgeTasks.new do |rubyforge|
|
38
|
+
end
|
39
|
+
rescue LoadError
|
40
|
+
puts "Jeweler not available. Install it with: gem install jeweler"
|
41
|
+
end
|
42
|
+
|
data/init.rb
ADDED
data/install.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# Install hook code here
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
class Base
|
3
|
+
class << self
|
4
|
+
if method_defined? :attribute_condition
|
5
|
+
def attribute_condition_with_postgresql_arrays(quoted_column_name, argument)
|
6
|
+
if ::PGArrays::PgArray === argument
|
7
|
+
case argument
|
8
|
+
when ::PGArrays::PgAny then "#{quoted_column_name} && ?"
|
9
|
+
when ::PGArrays::PgAll then "#{quoted_column_name} @> ?"
|
10
|
+
when ::PGArrays::PgIncludes then "#{quoted_column_name} <@ ?"
|
11
|
+
else "#{quoted_column_name} = ?"
|
12
|
+
end
|
13
|
+
else
|
14
|
+
attribute_condition_without_postgresql_arrays(quoted_column_name, argument)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
alias_method_chain :attribute_condition, :postgresql_arrays
|
18
|
+
end
|
19
|
+
|
20
|
+
def quote_bound_value_with_postgresql_arrays(value)
|
21
|
+
if ::PGArrays::PgArray === value
|
22
|
+
connection.quote_array_by_base_type(value, value.base_type)
|
23
|
+
else
|
24
|
+
quote_bound_value_without_postgresql_arrays(value)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
alias_method_chain :quote_bound_value, :postgresql_arrays
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
module PGArrays
|
33
|
+
class PgArray < Array
|
34
|
+
attr_reader :base_type
|
35
|
+
|
36
|
+
def initialize(array, type=nil)
|
37
|
+
super(array)
|
38
|
+
@base_type = type if type
|
39
|
+
end
|
40
|
+
|
41
|
+
def base_type
|
42
|
+
@base_type || :other
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class PgAny < PgArray; end
|
47
|
+
class PgAll < PgArray; end
|
48
|
+
class PgIncludes < PgArray; end
|
49
|
+
|
50
|
+
if defined? CanCan::Ability
|
51
|
+
class PgAny
|
52
|
+
def include?(v)
|
53
|
+
Array === v && !( v & self ).empty?
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class PgAll
|
58
|
+
def include?
|
59
|
+
Array === v && (self - v).empty?
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class PgIncludes < PgArray
|
64
|
+
def include?
|
65
|
+
Array === v && (v - self).empty?
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class Array
|
72
|
+
|
73
|
+
def pg(type=nil)
|
74
|
+
::PGArrays::PgArray.new(self, type)
|
75
|
+
end
|
76
|
+
|
77
|
+
def search_any(type=nil)
|
78
|
+
::PGArrays::PgAny.new(self, type)
|
79
|
+
end
|
80
|
+
|
81
|
+
def search_all(type=nil)
|
82
|
+
::PGArrays::PgAll.new(self, type)
|
83
|
+
end
|
84
|
+
|
85
|
+
def search_subarray(type=nil)
|
86
|
+
::PGArrays::PgIncludes.new(self, type)
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Arel
|
2
|
+
module Predicates
|
3
|
+
class ArrayAny < Binary
|
4
|
+
def eval(row)
|
5
|
+
!(operand1.eval(row) & operand2.eval(row)).empty?
|
6
|
+
end
|
7
|
+
|
8
|
+
def predicate_sql
|
9
|
+
"&&"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class ArrayAll < Binary
|
14
|
+
def eval(row)
|
15
|
+
(operand2.eval(row) - operand1.eval(row)).empty?
|
16
|
+
end
|
17
|
+
|
18
|
+
def predicate_sql
|
19
|
+
"@>"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class ArrayIncluded < Binary
|
24
|
+
def eval(row)
|
25
|
+
(operand1.eval(row) - operand2.eval(row)).empty?
|
26
|
+
end
|
27
|
+
|
28
|
+
def predicate_sql
|
29
|
+
"<@"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class Attribute
|
35
|
+
module Predications
|
36
|
+
def ar_any(other)
|
37
|
+
Predicates::ArrayAny.new(self, other)
|
38
|
+
end
|
39
|
+
|
40
|
+
def ar_all(other)
|
41
|
+
Predicates::ArrayAll.new(self, other)
|
42
|
+
end
|
43
|
+
|
44
|
+
def ar_included(other)
|
45
|
+
Predicates::ArrayIncluded.new(self, other)
|
46
|
+
end
|
47
|
+
|
48
|
+
def in(other)
|
49
|
+
case other
|
50
|
+
when ::PGArrays::PgAny
|
51
|
+
ar_any(other)
|
52
|
+
when ::PGArrays::PgAll
|
53
|
+
ar_all(other)
|
54
|
+
when ::PGArrays::PgIncludes
|
55
|
+
ar_included(other)
|
56
|
+
else
|
57
|
+
Predicates::In.new(self, other)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
module PGArrays
|
65
|
+
class PgArray
|
66
|
+
def to_sql( formatter = nil )
|
67
|
+
formatter.engine.quote_array_for_arel_by_base_type(self, base_type)
|
68
|
+
end
|
69
|
+
|
70
|
+
def to_a
|
71
|
+
self
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module PGArrays
|
2
|
+
module ReferencesBy
|
3
|
+
class RelationHolder < Struct.new(:relation, :field, :klass)
|
4
|
+
|
5
|
+
def referenced(obj)
|
6
|
+
ids = obj.read_attribute(field) || []
|
7
|
+
klass.find( ids.sort ).sort_by{|o| ids.index(o.id)}
|
8
|
+
end
|
9
|
+
|
10
|
+
def set_referenced(obj, value)
|
11
|
+
value = value.map{|v|
|
12
|
+
case v
|
13
|
+
when ActiveRecord::Base then v.id
|
14
|
+
when nil then nil
|
15
|
+
else v.to_i
|
16
|
+
end
|
17
|
+
}.compact
|
18
|
+
obj.write_attribute( field, value )
|
19
|
+
end
|
20
|
+
|
21
|
+
def validate(obj)
|
22
|
+
has_ids = klass.find(:all, :select=>'id', :conditions=>{:id=>obj.read_attribute(field)}).map(&:id)
|
23
|
+
unless has_ids.sort == obj.read_attribute(field).sort
|
24
|
+
obj.errors.add(relation, :wrong_array_reference)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def for_referenced(obj_klass)
|
29
|
+
condition = lambda do |obj|
|
30
|
+
{ :conditions=>{field.to_sym => case obj when klass then obj.id else obj.to_i end} }
|
31
|
+
end
|
32
|
+
obj_klass.send(:named_scope, "#{relation}_include", condition )
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
module ClassMethods
|
38
|
+
def references_by_array( relation, options = {} )
|
39
|
+
unless ActiveSupport::Memoizable === self
|
40
|
+
extend ActiveSupport::Memoizable
|
41
|
+
end
|
42
|
+
|
43
|
+
relation = relation.to_s.pluralize
|
44
|
+
field = "#{relation.singularize}_ids"
|
45
|
+
klass_name = (options[:class_name] || relation).to_s.singularize.camelize
|
46
|
+
klass = klass_name.constantize
|
47
|
+
holder = RelationHolder.new(relation, field, klass )
|
48
|
+
|
49
|
+
define_method(relation) do
|
50
|
+
holder.referenced(self)
|
51
|
+
end
|
52
|
+
memoize relation
|
53
|
+
|
54
|
+
define_method("#{relation}=") do |value|
|
55
|
+
flush_cache(relation)
|
56
|
+
holder.set_referenced(self, value)
|
57
|
+
end
|
58
|
+
|
59
|
+
if options[:validate]
|
60
|
+
validate {|o| holder.validate(o)}
|
61
|
+
end
|
62
|
+
|
63
|
+
holder.for_referenced(self)
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
ActiveRecord::Base.extend PGArrays::ReferencesBy::ClassMethods
|
71
|
+
|
@@ -0,0 +1,237 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
class PostgreSQLColumn < Column #:nodoc:
|
4
|
+
BASE_TYPE_COLUMNS = Hash.new{|h, base_type|
|
5
|
+
base_column= new(nil, nil, base_type, true)
|
6
|
+
h[base_type] = h[base_column.type]= base_column
|
7
|
+
}
|
8
|
+
attr_reader :base_column
|
9
|
+
|
10
|
+
def initialize(name, default, sql_type = nil, null = true)
|
11
|
+
if sql_type =~ /^(.+)\[\]$/
|
12
|
+
@base_sql_type = $1
|
13
|
+
@base_column = BASE_TYPE_COLUMNS[@base_sql_type]
|
14
|
+
end
|
15
|
+
super(name, self.class.extract_value_from_default(default), sql_type, null)
|
16
|
+
end
|
17
|
+
|
18
|
+
def simplified_type_with_postgresql_arrays(field_type)
|
19
|
+
if field_type=~/^(.+)\[\]$/
|
20
|
+
:"#{simplified_type_without_postgresql_arrays($1)}_array"
|
21
|
+
else
|
22
|
+
simplified_type_without_postgresql_arrays(field_type)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
alias_method_chain :simplified_type, :postgresql_arrays
|
26
|
+
|
27
|
+
def klass
|
28
|
+
if type.to_s =~ /_array$/
|
29
|
+
Array
|
30
|
+
else
|
31
|
+
super
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def type_cast(value)
|
36
|
+
return nil if value.nil?
|
37
|
+
case type
|
38
|
+
when :integer_array, :float_array
|
39
|
+
self.class.string_to_num_array(value)
|
40
|
+
when :decimal_array, :date_array, :boolean_array
|
41
|
+
safe_string_to_array(value)
|
42
|
+
when :timestamp_array, :time_array, :datetime_array, :binary_array
|
43
|
+
string_to_array(value)
|
44
|
+
when :text_array, :string_array
|
45
|
+
self.class.string_to_text_array(value)
|
46
|
+
else super
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def type_cast_code(var_name)
|
51
|
+
case type
|
52
|
+
when :integer_array, :float_array
|
53
|
+
"#{self.class.name}.string_to_num_array(#{var_name})"
|
54
|
+
when :decimal_array, :date_array, :boolean_array
|
55
|
+
"#{self.class.name}.safe_string_to_array(#{var_name}, #{@base_sql_type.inspect})"
|
56
|
+
when :timestamp_array, :time_array, :datetime_array, :binary_array
|
57
|
+
"#{self.class.name}.string_to_array(#{var_name}, #{@base_sql_type.inspect})"
|
58
|
+
when :text_array, :string_array
|
59
|
+
"#{self.class.name}.string_to_text_array(#{var_name})"
|
60
|
+
else super
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def safe_string_to_array(string)
|
65
|
+
return string unless string.is_a? String
|
66
|
+
return nil if string.empty?
|
67
|
+
|
68
|
+
string[1...-1].split(',').map{|v| @base_column.type_cast(v)}
|
69
|
+
end
|
70
|
+
|
71
|
+
def string_to_array(string)
|
72
|
+
return string unless string.is_a? String
|
73
|
+
return nil if string.empty?
|
74
|
+
|
75
|
+
self.class.string_to_text_array(string).map{|v| @base_column.type_cast(v)}
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.safe_string_to_array(string, sql_type)
|
79
|
+
return string unless string.is_a? String
|
80
|
+
return nil if string.empty?
|
81
|
+
|
82
|
+
base_column = BASE_TYPE_COLUMNS[sql_type]
|
83
|
+
string[1...-1].split(',').map{|v| base_column.type_cast(v)}
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.string_to_array(string, sql_type)
|
87
|
+
return string unless string.is_a? String
|
88
|
+
return nil if string.empty?
|
89
|
+
|
90
|
+
base_column = BASE_TYPE_COLUMNS[sql_type]
|
91
|
+
string_to_text_array( string ).map{|v| base_column.type_cast(v)}
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.string_to_num_array(string)
|
95
|
+
return string unless string.is_a? String
|
96
|
+
return nil if string.empty?
|
97
|
+
|
98
|
+
eval(string.tr('{}','[]'))
|
99
|
+
end
|
100
|
+
|
101
|
+
SARRAY_QUOTED = /^"(.*[^\\])?"$/m
|
102
|
+
SARRAY_PARTIAL = /^".*(\\"|[^"])$/m
|
103
|
+
def self.string_to_text_array(value)
|
104
|
+
return value unless value.is_a? String
|
105
|
+
return nil if value.empty?
|
106
|
+
|
107
|
+
values = value[1...-1].split(',')
|
108
|
+
partial = false
|
109
|
+
values.inject([]) do |res, s|
|
110
|
+
if partial
|
111
|
+
s = res.pop << ",#{s}"
|
112
|
+
elsif s=~ SARRAY_PARTIAL
|
113
|
+
partial = true
|
114
|
+
end
|
115
|
+
if s =~ SARRAY_QUOTED
|
116
|
+
s = eval(s)
|
117
|
+
partial = false
|
118
|
+
elsif s == 'NULL'
|
119
|
+
s = nil
|
120
|
+
end
|
121
|
+
res << s
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
class PostgreSQLAdapter #:nodoc:
|
127
|
+
def quote_with_postgresql_arrays(value, column = nil)
|
128
|
+
if Array === value && column && "#{column.type}" =~ /^(.+)_array$/
|
129
|
+
quote_array_by_base_type(value, $1, column)
|
130
|
+
else
|
131
|
+
quote_without_postgresql_arrays(value, column)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
alias_method_chain :quote, :postgresql_arrays
|
135
|
+
|
136
|
+
def quote_array_by_base_type(value, base_type, column = nil)
|
137
|
+
case base_type.to_sym
|
138
|
+
when :integer, :float, :decimal, :boolean, :date, :safe,
|
139
|
+
:string, :text, :other, :datetime, :timestamp, :time
|
140
|
+
quote_array_for_arel_by_base_type( value, base_type )
|
141
|
+
else
|
142
|
+
"E'#{ prepare_pg_string_array(value, base_type, column) }'"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def quote_array_for_arel_by_base_type( value, base_type )
|
147
|
+
case base_type.to_sym
|
148
|
+
when :integer, :float, :decimal, :boolean, :date, :safe, :datetime, :timestamp, :time
|
149
|
+
"'#{ prepare_array_for_arel_by_base_type(value, base_type) }'"
|
150
|
+
when :string, :text, :other
|
151
|
+
pa = prepare_array_for_arel_by_base_type(value, base_type)
|
152
|
+
"E'#{ quote_string( pa ) }'"
|
153
|
+
else
|
154
|
+
raise "Unsupported array base type #{base_type} for arel"
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def prepare_array_for_arel_by_base_type(value, base_type)
|
159
|
+
case base_type.to_sym
|
160
|
+
when :integer
|
161
|
+
prepare_pg_integer_array(value)
|
162
|
+
when :float
|
163
|
+
prepare_pg_float_array(value)
|
164
|
+
when :string, :text, :other
|
165
|
+
prepare_pg_text_array(value)
|
166
|
+
when :datetime, :timestamp, :time
|
167
|
+
prepare_pg_string_array(value, base_type)
|
168
|
+
when :decimal, :boolean, :date, :safe
|
169
|
+
prepare_pg_string_safe_array(value)
|
170
|
+
else
|
171
|
+
raise "Unsupported array base type #{base_type} for arel"
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def prepare_pg_integer_array(value)
|
176
|
+
"{#{ value.map{|v| v.nil? ? 'NULL' : v.to_i}.join(',')}}"
|
177
|
+
end
|
178
|
+
|
179
|
+
def prepare_pg_float_array(value)
|
180
|
+
"{#{ value.map{|v| v.nil? ? 'NULL' : v.to_f}.join(',')}}"
|
181
|
+
end
|
182
|
+
|
183
|
+
def prepare_pg_string_safe_array(value)
|
184
|
+
"{#{ value.map{|v| v.nil? ? 'NULL' : v.to_s}.join(',')}}"
|
185
|
+
end
|
186
|
+
|
187
|
+
def prepare_pg_string_array(value, base_type, column=nil)
|
188
|
+
base_column= if column
|
189
|
+
column.base_column
|
190
|
+
else
|
191
|
+
PostgreSQLColumn::BASE_TYPE_COLUMNS[base_type.to_sym]
|
192
|
+
end
|
193
|
+
value = value.map do|v|
|
194
|
+
v = quote_without_postgresql_arrays(v, base_column)
|
195
|
+
if v=~/^E?'(.+)'$/ then v = $1 end
|
196
|
+
"\"#{v.gsub('"','\"')}\""
|
197
|
+
end
|
198
|
+
"{#{ value.join(',')}}"
|
199
|
+
end
|
200
|
+
|
201
|
+
def prepare_pg_text_array(value)
|
202
|
+
value = value.map{|v|
|
203
|
+
v ? v.to_s.gsub('\\','\\\\\\').gsub('"','\"') : 'NULL'
|
204
|
+
}.inspect
|
205
|
+
value.tr!('[]','{}')
|
206
|
+
end
|
207
|
+
|
208
|
+
|
209
|
+
NATIVE_DATABASE_TYPES.keys.each do |key|
|
210
|
+
unless key==:primary_key
|
211
|
+
base = NATIVE_DATABASE_TYPES[key].dup
|
212
|
+
base[:name] = base[:name]+'[]'
|
213
|
+
NATIVE_DATABASE_TYPES[:"#{key}_array"]= base
|
214
|
+
TableDefinition.class_eval <<-EOV
|
215
|
+
def #{key}_array(*args) # def string_array(*args)
|
216
|
+
options = args.extract_options! # options = args.extract_options!
|
217
|
+
column_names = args # column_names = args
|
218
|
+
#
|
219
|
+
column_names.each { |name| column(name, '#{key}_array', options) }# column_names.each { |name| column(name, 'string_array', options) }
|
220
|
+
end # end
|
221
|
+
EOV
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
def type_to_sql_with_postgresql_arrays(type, limit = nil, precision = nil, scale = nil)
|
226
|
+
if type.to_s =~ /^(.+)_array$/
|
227
|
+
type_to_sql_without_postgresql_arrays($1, limit, precision, scale)+'[]'
|
228
|
+
else
|
229
|
+
type_to_sql_without_postgresql_arrays(type, limit, precision, scale)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
alias_method_chain :type_to_sql, :postgresql_arrays
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
class PostgreSQLAdapter
|
4
|
+
def prepare_for_arel( value, column )
|
5
|
+
return value unless value
|
6
|
+
if Array === value && "#{column.type}" =~ /^(.+)_array$/
|
7
|
+
prepare_array_for_arel_by_base_type(value, $1)
|
8
|
+
else
|
9
|
+
super
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module ActiveRecord
|
17
|
+
# I hope ticket 5047 will be included in Rails 3 reliz
|
18
|
+
unless ConnectionAdapters::AbstractAdapter.method_defined? :prepare_for_arel
|
19
|
+
module ConnectionAdapters
|
20
|
+
class AbstractAdapter
|
21
|
+
def prepare_for_arel( value, column )
|
22
|
+
if value && ((value.acts_like?(:date) || value.acts_like?(:time)) || value.is_a?(Hash) || value.is_a?(Array))
|
23
|
+
value.to_yaml
|
24
|
+
else
|
25
|
+
value
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class Base
|
32
|
+
private
|
33
|
+
# Returns a copy of the attributes hash where all the values have been safely quoted for use in
|
34
|
+
# an Arel insert/update method.
|
35
|
+
def arel_attributes_values(include_primary_key = true, include_readonly_attributes = true, attribute_names = @attributes.keys)
|
36
|
+
attrs = {}
|
37
|
+
attribute_names.each do |name|
|
38
|
+
if (column = column_for_attribute(name)) && (include_primary_key || !column.primary)
|
39
|
+
|
40
|
+
if include_readonly_attributes || (!include_readonly_attributes && !self.class.readonly_attributes.include?(name))
|
41
|
+
value = read_attribute(name)
|
42
|
+
|
43
|
+
if value && self.class.serialized_attributes.has_key?(name)
|
44
|
+
value = value.to_yaml
|
45
|
+
else
|
46
|
+
value = self.class.connection.prepare_for_arel(value, column)
|
47
|
+
end
|
48
|
+
attrs[self.class.arel_table[name]] = value
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
attrs
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
module Arel
|
59
|
+
module Attributes
|
60
|
+
%w{Integer Float Decimal Boolean String Time}.each do |basetype|
|
61
|
+
module_eval <<-"END"
|
62
|
+
class #{basetype}Array < Attribute
|
63
|
+
end
|
64
|
+
END
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
module Sql
|
69
|
+
module Attributes
|
70
|
+
class << self
|
71
|
+
def for_with_postgresql_arrays(column)
|
72
|
+
if column.type.to_s =~ /^(.+)_array$/
|
73
|
+
('Arel::Sql::Attributes::' + for_without_postgresql_arrays(column.base_column).name.split('::').last + 'Array').constantize
|
74
|
+
else
|
75
|
+
for_without_postgresql_arrays(column)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
alias_method_chain :for, :postgresql_arrays
|
79
|
+
end
|
80
|
+
|
81
|
+
%w{Integer Float Decimal Boolean String Time}.each do |basetype|
|
82
|
+
module_eval <<-"END"
|
83
|
+
class #{basetype}Array < Arel::Attributes::#{basetype}Array
|
84
|
+
include Attributes
|
85
|
+
end
|
86
|
+
END
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
data/lib/ar_pg_array.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'active_record/base'
|
3
|
+
require 'active_record/connection_adapters/postgresql_adapter'
|
4
|
+
|
5
|
+
require 'ar_pg_array/schema'
|
6
|
+
require 'ar_pg_array/querying'
|
7
|
+
require 'ar_pg_array/references_by'
|
8
|
+
if defined? ::Arel
|
9
|
+
require 'ar_pg_array/schema_arel'
|
10
|
+
require 'ar_pg_array/querying_arel'
|
11
|
+
end
|
data/uninstall.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# Uninstall hook code here
|
metadata
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ar_pg_array
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 63
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 8
|
9
|
+
- 0
|
10
|
+
version: 0.8.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Sokolov Yura aka funny_falcon
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-07-06 00:00:00 +04:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: activerecord
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 9
|
30
|
+
segments:
|
31
|
+
- 2
|
32
|
+
- 3
|
33
|
+
- 5
|
34
|
+
version: 2.3.5
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
description: ar_pg_array includes support of PostgreSQL's int[], float[], text[], timestamptz[] etc. into ActiveRecord. You could define migrations for array columns, query on array columns.
|
38
|
+
email: funny.falcon@gmail.com
|
39
|
+
executables: []
|
40
|
+
|
41
|
+
extensions: []
|
42
|
+
|
43
|
+
extra_rdoc_files:
|
44
|
+
- README
|
45
|
+
files:
|
46
|
+
- MIT-LICENSE
|
47
|
+
- README
|
48
|
+
- Rakefile
|
49
|
+
- init.rb
|
50
|
+
- install.rb
|
51
|
+
- lib/ar_pg_array.rb
|
52
|
+
- lib/ar_pg_array/querying.rb
|
53
|
+
- lib/ar_pg_array/querying_arel.rb
|
54
|
+
- lib/ar_pg_array/references_by.rb
|
55
|
+
- lib/ar_pg_array/schema.rb
|
56
|
+
- lib/ar_pg_array/schema_arel.rb
|
57
|
+
- uninstall.rb
|
58
|
+
has_rdoc: true
|
59
|
+
homepage: http://github.com/funny-falcon/activerecord-postgresql-arrays
|
60
|
+
licenses: []
|
61
|
+
|
62
|
+
post_install_message:
|
63
|
+
rdoc_options:
|
64
|
+
- --charset=UTF-8
|
65
|
+
require_paths:
|
66
|
+
- lib
|
67
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
68
|
+
none: false
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
hash: 3
|
73
|
+
segments:
|
74
|
+
- 0
|
75
|
+
version: "0"
|
76
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
77
|
+
none: false
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
hash: 3
|
82
|
+
segments:
|
83
|
+
- 0
|
84
|
+
version: "0"
|
85
|
+
requirements: []
|
86
|
+
|
87
|
+
rubyforge_project: ar_pg_array
|
88
|
+
rubygems_version: 1.3.7
|
89
|
+
signing_key:
|
90
|
+
specification_version: 3
|
91
|
+
summary: Use power of PostgreSQL Arrays in ActiveRecord
|
92
|
+
test_files: []
|
93
|
+
|