ar_pg_array 0.8.0
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/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
|
+
|