ar_pg_array 0.9.13 → 0.10.1

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -30,7 +30,7 @@ begin
30
30
  gemspec.email = "funny.falcon@gmail.com"
31
31
  gemspec.homepage = "http://github.com/funny-falcon/activerecord-postgresql-arrays"
32
32
  gemspec.authors = ["Sokolov Yura aka funny_falcon"]
33
- gemspec.add_dependency('activerecord', '>= 2.3.5')
33
+ gemspec.add_dependency('activerecord', '>= 2.3.5', '<3.0')
34
34
  gemspec.rubyforge_project = 'ar-pg-array'
35
35
  end
36
36
  Jeweler::GemcutterTasks.new
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.9.13
1
+ 0.10.0
data/lib/ar_pg_array.rb CHANGED
@@ -7,9 +7,4 @@ require 'ar_pg_array/schema_cacheable'
7
7
  require 'ar_pg_array/querying'
8
8
  require 'ar_pg_array/allways_save'
9
9
  require 'ar_pg_array/references_by'
10
- if ActiveRecord::VERSION::MAJOR >= 3
11
- require 'ar_pg_array/schema_arel'
12
- require 'ar_pg_array/querying_arel'
13
- else
14
- require 'ar_pg_array/schema_fix_will_change'
15
- end
10
+ require 'ar_pg_array/schema_fix_will_change'
@@ -1,37 +1,21 @@
1
1
  module ActiveRecord
2
- module CheckArrayBeforeUpdate
3
- def mark_arrays_for_update
4
- @attributes_cache.each do |name, value|
5
- attribute_will_change!(name) if Array === value && _read_attribute(name) != value
6
- end
2
+ module CheckArrayBeforeUpdate
3
+ def mark_arrays_for_update
4
+ @attributes_cache.each do |name, value|
5
+ attribute_will_change!(name) if Array === value && _read_attribute(name) != value
7
6
  end
8
7
  end
9
- if VERSION::MAJOR < 3
10
- module CheckArrayBeforeUpdate
11
- def self.included(base)
12
- base.alias_method_chain :update, :check_array
13
- base.send(:alias_method, :_read_attribute, :read_attribute)
14
- end
8
+ end
15
9
 
16
- def update_with_check_array
17
- mark_arrays_for_update
18
- update_without_check_array
19
- end
10
+ module CheckArrayBeforeUpdate
11
+ def self.included(base)
12
+ base.alias_method_chain :update, :check_array
13
+ base.send(:alias_method, :_read_attribute, :read_attribute)
20
14
  end
21
- else
22
- module CheckArrayBeforeUpdate
23
- include ActiveSupport::Concern
24
15
 
25
- if VERSION::MAJOR == 3 && VERSION::MINOR >= 2
26
- def _read_attribute(attr_name)
27
- column_for_attribute(attr_name).type_cast(@attributes[attr_name])
28
- end
29
- end
30
-
31
- def update(*)
32
- mark_arrays_for_update
33
- super
34
- end
16
+ def update_with_check_array
17
+ mark_arrays_for_update
18
+ update_without_check_array
35
19
  end
36
20
  end
37
21
  Base.__send__ :include, CheckArrayBeforeUpdate
@@ -0,0 +1,160 @@
1
+ require 'json'
2
+
3
+ module PgArrayParser
4
+ CURLY_BRACKETS = '{}'.freeze
5
+ SQUARE_BRACKETS = '[]'.freeze
6
+ NULL = 'NULL'.freeze
7
+ NIL = 'nil'.freeze
8
+ ESCAPE_HASH={'\\'.freeze=>'\\\\'.freeze, '"'.freeze=>'\\"'.freeze}
9
+
10
+ def parse_numeric_pgarray(text)
11
+ text = text.tr(CURLY_BRACKETS, SQUARE_BRACKETS)
12
+ text.downcase!
13
+ JSON.load(text)
14
+ end
15
+
16
+ def parse_safe_pgarray(text, &block)
17
+ if text =~ /^\{([^\}]*)\}$/
18
+ $1.split(/,\s*/).map!{|v| v == NULL ? nil : yield(v)}
19
+ else
20
+ raise "Mailformed array" unless text =~ /^\{\s*/
21
+ ar, rest = _parse_safe_pgarray($')
22
+ ar
23
+ end
24
+ end
25
+
26
+ def _parse_safe_pgarray(text, &block)
27
+ values = []
28
+ return values if text =~ /^\}\s*/
29
+ if text =~ /^\{\s*/
30
+ text = $'
31
+ while true
32
+ ar, rest = _parse_safe_pgarray(text, &block)
33
+ values << ar
34
+ if rest =~ /^\}\s*/
35
+ return values, $'
36
+ elsif rest =~ /^,\s*/
37
+ rest = $'
38
+ else
39
+ raise "Mailformed postgres array"
40
+ end
41
+ text = rest
42
+ end
43
+ else
44
+ while true
45
+ raise 'Mailformed Array' unless text =~ /^([^,\}]*)([,\}])\s*/
46
+ val, sep, rest = $1, $2, $'
47
+ values << (val == NULL ? nil : yield(val))
48
+ if sep == '}'
49
+ return values, rest
50
+ end
51
+ text = rest
52
+ end
53
+ end
54
+ end
55
+
56
+ def parse_pgarray(text, &block)
57
+ raise "Mailformed postgres array" unless text =~ /^\{\s*/
58
+ ar, rest = _parse_pgarray($', &block)
59
+ ar
60
+ end
61
+
62
+ def _parse_pgarray(text, &block)
63
+ values = []
64
+ return values if text =~ /^\}\s*/
65
+ if text =~ /^\{\s*/
66
+ text = $'
67
+ while true
68
+ ar, rest = _parse_pgarray(text, &block)
69
+ values << ar
70
+ if rest =~ /^\}\s*/
71
+ return values, $'
72
+ elsif rest =~ /^,\s*{\s*/
73
+ rest = $'
74
+ else
75
+ raise "Mailformed postgres array"
76
+ end
77
+ text = rest
78
+ end
79
+ else
80
+ while true
81
+ if text =~ /^"((?:\\.|[^"\\])*)"([,}])\s*/
82
+ val, sep, rest = $1, $2, $'
83
+ val.gsub!(/\\(.)/, '\1')
84
+ val = yield val
85
+ elsif text =~ /^([^,\}]*)([,}])\s*/
86
+ val, sep, rest = $1, $2, $'
87
+ val = val == NULL ? nil : yield(val)
88
+ else
89
+ raise "Mailformed postgres array"
90
+ end
91
+ values << val
92
+ if sep == '}'
93
+ return values, rest
94
+ end
95
+ text = rest
96
+ end
97
+ end
98
+ end
99
+
100
+ def prepare_pg_integer_array(value)
101
+ val = value.map{|v| v.nil? ? nil : v.to_i}.inspect
102
+ val.gsub!(NIL, NULL)
103
+ val.tr!(SQUARE_BRACKETS, CURLY_BRACKETS)
104
+ val
105
+ end
106
+
107
+ def prepare_pg_float_array(value)
108
+ val = value.map{|v| v.nil? ? nil : v.to_f}.inspect
109
+ val.gsub!(NIL, NULL)
110
+ val.tr!(SQUARE_BRACKETS, CURLY_BRACKETS)
111
+ val
112
+ end
113
+
114
+ def prepare_pg_safe_array(value)
115
+ value = value.map{|val|
116
+ case val
117
+ when Array
118
+ prepare_pg_safe_array(val)
119
+ when nil
120
+ NULL
121
+ else
122
+ val.to_s
123
+ end
124
+ }.join(',')
125
+ "{#{value}}"
126
+ end
127
+
128
+ def prepare_pg_text_array(value)
129
+ value = value.map{|val|
130
+ case val
131
+ when Array
132
+ prepare_pg_text_array(val)
133
+ when nil
134
+ NULL
135
+ else
136
+ "\"#{val.to_s.gsub(/\\|"/){|s| ESCAPE_HASH[s]}}\""
137
+ end
138
+ }.join(',')
139
+ "{#{value}}"
140
+ end
141
+
142
+ def prepare_pg_string_array(value, &block)
143
+ value = value.map{|val|
144
+ case val
145
+ when Array
146
+ prepare_pg_string_array(val, &block)
147
+ when nil
148
+ NULL
149
+ else
150
+ val = yield val
151
+ if val =~ /^'(.*)'$/m
152
+ "\"#{ $1.gsub(/\\|"/){|s| ESCAPE_HASH[s]} }\""
153
+ else
154
+ val
155
+ end
156
+ end
157
+ }.join(',')
158
+ "{#{value}}"
159
+ end
160
+ end
@@ -1,37 +1,25 @@
1
1
  module ActiveRecord
2
2
  class Base
3
3
  class << self
4
- if private_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)
4
+ def attribute_condition_with_postgresql_arrays(quoted_column_name, argument)
5
+ if ::PGArrays::PgArray === argument
6
+ case argument
7
+ when ::PGArrays::PgAny then "#{quoted_column_name} && ?"
8
+ when ::PGArrays::PgAll then "#{quoted_column_name} @> ?"
9
+ when ::PGArrays::PgIncludes then "#{quoted_column_name} <@ ?"
10
+ else "#{quoted_column_name} = ?"
15
11
  end
12
+ else
13
+ attribute_condition_without_postgresql_arrays(quoted_column_name, argument)
16
14
  end
17
- alias_method_chain :attribute_condition, :postgresql_arrays
18
15
  end
16
+ alias_method_chain :attribute_condition, :postgresql_arrays
19
17
 
20
- if instance_method(:quote_bound_value).arity == 1
21
- def quote_bound_value_with_postgresql_arrays(value)
22
- if ::PGArrays::PgArray === value
23
- connection.quote_array_by_base_type(value, value.base_type)
24
- else
25
- quote_bound_value_without_postgresql_arrays(value)
26
- end
27
- end
28
- else
29
- def quote_bound_value_with_postgresql_arrays(value, c = connection)
30
- if ::PGArrays::PgArray === value
31
- c.quote_array_by_base_type(value, value.base_type)
32
- else
33
- quote_bound_value_without_postgresql_arrays(value, c)
34
- end
18
+ def quote_bound_value_with_postgresql_arrays(value)
19
+ if ::PGArrays::PgArray === value
20
+ connection.quote_array_by_base_type(value, value.base_type)
21
+ else
22
+ quote_bound_value_without_postgresql_arrays(value)
35
23
  end
36
24
  end
37
25
  alias_method_chain :quote_bound_value, :postgresql_arrays
@@ -1,12 +1,16 @@
1
+ require 'ar_pg_array/parser'
1
2
  module ActiveRecord
2
3
  module ConnectionAdapters
3
4
  class PostgreSQLColumn < Column #:nodoc:
5
+ include PgArrayParser
6
+ extend PgArrayParser
7
+
4
8
  BASE_TYPE_COLUMNS = Hash.new{|h, base_type|
5
9
  base_column= new(nil, nil, base_type.to_s, true)
6
10
  h[base_type] = h[base_column.type]= base_column
7
11
  }
8
12
  attr_reader :base_column
9
-
13
+
10
14
  def initialize(name, default, sql_type = nil, null = true)
11
15
  if sql_type =~ /^(.+)\[\]$/
12
16
  @base_sql_type = $1
@@ -14,7 +18,7 @@ module ActiveRecord
14
18
  end
15
19
  super(name, self.class.extract_value_from_default(default), sql_type, null)
16
20
  end
17
-
21
+
18
22
  def simplified_type_with_postgresql_arrays(field_type)
19
23
  if field_type=~/^(.+)\[\]$/
20
24
  :"#{simplified_type_without_postgresql_arrays($1)}_array"
@@ -36,17 +40,17 @@ module ActiveRecord
36
40
  return nil if value.nil?
37
41
  case type
38
42
  when :integer_array, :float_array
39
- self.class.string_to_num_array(value)
43
+ string_to_num_array(value)
40
44
  when :decimal_array, :date_array, :boolean_array
41
45
  safe_string_to_array(value)
42
46
  when :timestamp_array, :time_array, :datetime_array, :binary_array
43
47
  string_to_array(value)
44
48
  when :text_array, :string_array
45
- self.class.string_to_text_array(value)
49
+ string_to_text_array(value)
46
50
  else super
47
51
  end
48
52
  end
49
-
53
+
50
54
  def type_cast_code(var_name)
51
55
  case type
52
56
  when :integer_array, :float_array
@@ -60,70 +64,79 @@ module ActiveRecord
60
64
  else super
61
65
  end
62
66
  end
63
-
64
- def safe_string_to_array(string)
67
+
68
+ def default
69
+ res = super
70
+ Array === res ? res.dup : res
71
+ end
72
+
73
+ def self._string_to_array(string)
65
74
  return string unless string.is_a? String
66
75
  return nil if string.empty?
67
-
68
- string[1...-1].split(',').map{|v| @base_column.type_cast(v)}
76
+
77
+ yield
69
78
  end
70
-
71
- def string_to_array(string)
79
+
80
+ def _string_to_array(string)
72
81
  return string unless string.is_a? String
73
82
  return nil if string.empty?
74
-
75
- self.class.string_to_text_array(string).map{|v| @base_column.type_cast(v)}
83
+
84
+ yield
85
+ end
86
+
87
+ def safe_string_to_array(string)
88
+ _string_to_array(string) do
89
+ parse_safe_pgarray(string){|v| @base_column.type_cast(v)}
90
+ end
76
91
  end
77
-
92
+
78
93
  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)}
94
+ _string_to_array(string) do
95
+ base_column = BASE_TYPE_COLUMNS[sql_type]
96
+ parse_safe_pgarray(string){|v| base_column.type_cast(v)}
97
+ end
98
+ end
99
+
100
+ def string_to_array(string)
101
+ _string_to_array(string) do
102
+ parse_pgarray(string){|v| @base_column.type_cast(v)}
103
+ end
84
104
  end
85
-
105
+
86
106
  def self.string_to_array(string, sql_type)
87
- return string unless string.is_a? String
88
- return nil if string.empty?
107
+ _string_to_array(string) do
108
+ base_column = BASE_TYPE_COLUMNS[sql_type]
109
+ parse_pgarray(string){|v| base_column.type_cast(v)}
110
+ end
111
+ end
89
112
 
90
- base_column = BASE_TYPE_COLUMNS[sql_type]
91
- string_to_text_array( string ).map{|v| base_column.type_cast(v)}
113
+ def string_to_num_array(string)
114
+ _string_to_array(string) do
115
+ parse_numeric_pgarray(string)
116
+ end
92
117
  end
93
-
118
+
94
119
  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('{}','[]'))
120
+ _string_to_array(string) do
121
+ parse_numeric_pgarray(string)
122
+ end
123
+ end
124
+
125
+ def string_to_text_array(string)
126
+ _string_to_array(string) do
127
+ parse_pgarray(string){|v| v}
128
+ end
99
129
  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 = $1.gsub(/\\(.)/,'\1')
117
- partial = false
118
- elsif s == 'NULL'
119
- s = nil
120
- end
121
- res << s
130
+
131
+ def self.string_to_text_array(string)
132
+ _string_to_array(string) do
133
+ parse_pgarray(string){|v| v}
122
134
  end
123
135
  end
124
136
  end
125
-
137
+
126
138
  class PostgreSQLAdapter #:nodoc:
139
+ include PgArrayParser
127
140
  def quote_with_postgresql_arrays(value, column = nil)
128
141
  if Array === value && column && "#{column.type}" =~ /^(.+)_array$/
129
142
  quote_array_by_base_type(value, $1, column)
@@ -132,29 +145,19 @@ module ActiveRecord
132
145
  end
133
146
  end
134
147
  alias_method_chain :quote, :postgresql_arrays
135
-
148
+
136
149
  def quote_array_by_base_type(value, base_type, column = nil)
137
150
  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 )
151
+ when :integer, :float, :decimal, :boolean, :date, :safe, :datetime, :timestamp, :time
152
+ "'#{ prepare_array_for_arel_by_base_type(value, base_type) }'"
153
+ when :string, :text, :other
154
+ pa = prepare_array_for_arel_by_base_type(value, base_type)
155
+ "'#{ quote_string( pa ) }'"
141
156
  else
142
157
  "'#{ prepare_pg_string_array(value, base_type, column) }'"
143
158
  end
144
159
  end
145
160
 
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
- "'#{ quote_string( pa ) }'"
153
- else
154
- raise "Unsupported array base type #{base_type} for arel"
155
- end
156
- end
157
-
158
161
  def prepare_array_for_arel_by_base_type(value, base_type)
159
162
  case base_type.to_sym
160
163
  when :integer
@@ -166,57 +169,21 @@ module ActiveRecord
166
169
  when :datetime, :timestamp, :time
167
170
  prepare_pg_string_array(value, base_type)
168
171
  when :decimal, :boolean, :date, :safe
169
- prepare_pg_string_safe_array(value)
172
+ prepare_pg_safe_array(value)
170
173
  else
171
174
  raise "Unsupported array base type #{base_type} for arel"
172
175
  end
173
176
  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
- ESCAPE_HASH={'\\'=>'\\\\', '"'=>'\\"'}
177
+
188
178
  def prepare_pg_string_array(value, base_type, column=nil)
189
179
  base_column= if column
190
180
  column.base_column
191
181
  else
192
182
  PostgreSQLColumn::BASE_TYPE_COLUMNS[base_type.to_sym]
193
183
  end
194
- value = value.map do|v|
195
- unless v.nil?
196
- v = quote_without_postgresql_arrays(v, base_column)
197
- if v=~/^'(.+)'$/m then
198
- "\"#{$1.gsub(/\\|"/){|s| ESCAPE_HASH[s]}}\""
199
- else
200
- v
201
- end
202
- else
203
- 'NULL'
204
- end
205
- end
206
- "{#{ value.join(',')}}"
184
+ super(value){|v| quote_without_postgresql_arrays(v, base_column)}
207
185
  end
208
186
 
209
- class CNULL; def inspect; 'NULL'; end; alias to_s inspect end
210
- NULL = CNULL.new
211
-
212
- TESCAPE_HASH={'\\'=>'\\\\', '"'=>'\\"'}
213
- def prepare_pg_text_array(value)
214
- value = value.map{|v|
215
- v ? "\"#{v.to_s.gsub(/\\|"/){|s| TESCAPE_HASH[s]}}\"" : NULL
216
- }.join(',')
217
- "{#{value}}"
218
- end
219
-
220
187
  NATIVE_DATABASE_TYPES.keys.each do |key|
221
188
  unless key==:primary_key
222
189
  base = NATIVE_DATABASE_TYPES[key].dup
@@ -242,7 +209,7 @@ module ActiveRecord
242
209
  EOV
243
210
  end
244
211
  end
245
-
212
+
246
213
  def add_column_with_postgresql_arrays( table, column, type, options = {} )
247
214
  if type.to_s =~ /^(.+)_array$/ && options[:default].is_a?(Array)
248
215
  options = options.merge(:default => prepare_array_for_arel_by_base_type(options[:default], $1))
@@ -250,7 +217,7 @@ module ActiveRecord
250
217
  add_column_without_postgresql_arrays( table, column, type, options )
251
218
  end
252
219
  alias_method_chain :add_column, :postgresql_arrays
253
-
220
+
254
221
  def type_to_sql_with_postgresql_arrays(type, limit = nil, precision = nil, scale = nil)
255
222
  if type.to_s =~ /^(.+)_array$/
256
223
  type_to_sql_without_postgresql_arrays($1.to_sym, limit, precision, scale)+'[]'
@@ -258,7 +225,7 @@ module ActiveRecord
258
225
  type_to_sql_without_postgresql_arrays(type, limit, precision, scale)
259
226
  end
260
227
  end
261
-
228
+
262
229
  alias_method_chain :type_to_sql, :postgresql_arrays
263
230
  end
264
231
  end
@@ -1,11 +1,6 @@
1
- adjust_cached_types = lambda do |atcbd|
2
- atcbd << /_array$/
3
- def atcbd.include?(val)
4
- any?{|type| type === val}
5
- end
6
- end
7
- if ActiveRecord::VERSION::MAJOR < 3
8
- adjust_cached_types.call(ActiveRecord::AttributeMethods::ATTRIBUTE_TYPES_CACHED_BY_DEFAULT)
9
- else
10
- adjust_cached_types.call(ActiveRecord::AttributeMethods::Read::ATTRIBUTE_TYPES_CACHED_BY_DEFAULT)
1
+ atcbd = ActiveRecord::AttributeMethods::ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
2
+
3
+ atcbd << /_array$/
4
+ def atcbd.include?(val)
5
+ any?{|type| type === val}
11
6
  end
@@ -1,3 +1,4 @@
1
1
  class Item < ActiveRecord::Base
2
2
  references_by_array :tags, :validate=>true
3
+ serialize :for_yaml
3
4
  end
@@ -1,22 +1,28 @@
1
1
  ActiveRecord::Schema.define do
2
- create_table "tags", :force => true do |t|
3
- t.string :name
4
- t.timestamps
5
- end
6
-
7
- create_table "items", :force => true do |t|
8
- t.string :value
9
- t.integer_array :tag_ids, :default => [1, 2]
10
- t.string_array :tag_names, :default => %w{as so}
11
- end
12
-
2
+ create_table "tags", :force => true do |t|
3
+ t.string :name
4
+ t.timestamps
5
+ end
6
+
7
+ create_table "items", :force => true do |t|
8
+ t.string :value
9
+ t.integer_array :tag_ids, :default => [1, 2]
10
+ t.string_array :tag_names, :default => %w{as so}
11
+ t.text :for_yaml
12
+ end
13
+
13
14
  create_table "bulks", :force => true do |t|
14
- t.string :value, :default => "'"
15
- t.integer_array :ints, :default => [1, 2]
16
- t.string_array :strings, :default => %w{as so}
17
- t.timestamp_array :times, :default => %w{2010-01-01 2010-02-01}
18
- t.float_array :floats, :default => [1.0, 1.2]
19
- t.decimal_array :decimals, :default => [1.0, 1.2]
15
+ t.string :value, :default => "'"
16
+ t.integer_array :ints, :default => [1, 2]
17
+ t.string_array :strings, :default => %w{as so}
18
+ t.timestamp_array :times, :default => %w{2010-01-01 2010-02-01}
19
+ t.float_array :floats, :default => [1.0, 1.2]
20
+ t.decimal_array :decimals, :default => [1.0, 1.2]
20
21
  t.text_array :texts, :default => [nil, 'Text', 'NULL', 'Text with nil', 'Text with , nil, !"\\', 'nil']
21
- end
22
+ end
23
+
24
+ create_table "unrelateds", :force => true do |t|
25
+ t.text :for_yaml
26
+ t.text :for_custom_serialize
27
+ end
22
28
  end
@@ -0,0 +1,21 @@
1
+ require 'base64'
2
+ class Unrelated < ActiveRecord::Base
3
+ class MySerializer
4
+ def initialize(val)
5
+ @val = val
6
+ end
7
+
8
+ def self.dump(obj)
9
+ Base64.encode64(Marshal.dump(obj))
10
+ end
11
+
12
+ def self.load(str)
13
+ if str
14
+ Marshal.load(Base64.decode64(str))
15
+ end
16
+ end
17
+ end
18
+
19
+ serialize :for_yaml
20
+ serialize :for_custom_serialize, MySerializer
21
+ end
@@ -0,0 +1,3 @@
1
+ item1:
2
+ id: 1
3
+ for_yaml: {"a": "b"}
@@ -83,6 +83,13 @@ describe "PgArray" do
83
83
  map_times(bulk.times).should ==
84
84
  map_times(parse_times(%w{2010-01-01 2010-02-01}))
85
85
  end
86
+
87
+ it "should not alter defaults" do
88
+ bulk = Bulk.new
89
+ bulk.strings.push :foo
90
+ bulk = Bulk.new
91
+ bulk.strings.should == %w{as so}
92
+ end
86
93
 
87
94
  it "should save changes" do
88
95
  bulk = Bulk.find(3)
@@ -152,6 +159,39 @@ describe "PgArray" do
152
159
  bulk.ints.should == new
153
160
  end
154
161
 
162
+ it 'should not break yaml serialization on model with array' do
163
+ item = Item.find(1)
164
+ item.for_yaml = {:a => :b}
165
+ item.save.should be_true
166
+ copy = Item.find(1)
167
+ copy.for_yaml.should == {:a => :b}
168
+ copy.for_yaml = ['a', 'b']
169
+ copy.save.should be_true
170
+ copy = Item.find(1)
171
+ copy.for_yaml.should == ['a', 'b']
172
+ end
173
+
174
+ it 'should not break yaml serialization on unrelated model' do
175
+ item = Unrelated.find(1)
176
+ item.for_yaml.should == {'a' => 'b'}
177
+ item.for_yaml = {:a => :b}
178
+ item.save.should be_true
179
+ copy = Unrelated.find(1)
180
+ copy.for_yaml.should == {:a => :b}
181
+ copy.for_yaml = ['a', 'b']
182
+ copy.save.should be_true
183
+ copy = Unrelated.find(1)
184
+ copy.for_yaml.should == ['a', 'b']
185
+ end
186
+
187
+ it 'should not break custom serialization on model with array' do
188
+ obj = Unrelated::MySerializer.new(%w{hello world})
189
+ model = Unrelated.create!(:for_custom_serialize => obj)
190
+ model.for_custom_serialize.to_yaml.should == obj.to_yaml
191
+ model2 = Unrelated.find(model.id)
192
+ model2.for_custom_serialize.to_yaml.should == obj.to_yaml
193
+ end
194
+
155
195
  def map_times(times)
156
196
  times.map{|t| t.strftime("%F %T")}
157
197
  end
data/spec/spec_helper.rb CHANGED
@@ -38,6 +38,7 @@ ActiveRecord::Base.logger = Logger.new(STDOUT) #if $0 == 'irb'
38
38
  require 'tag'
39
39
  require 'item'
40
40
  require 'bulk'
41
+ require 'unrelated'
41
42
  ActiveRecord::Base.silence do
42
43
  ActiveRecord::Migration.verbose = false
43
44
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ar_pg_array
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.13
4
+ version: 0.10.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,19 +9,30 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-02-11 00:00:00.000000000 Z
12
+ date: 2012-06-03 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activerecord
16
- requirement: &84224510 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
+ - - <
20
+ - !ruby/object:Gem::Version
21
+ version: '3.0'
19
22
  - - ! '>='
20
23
  - !ruby/object:Gem::Version
21
24
  version: 2.3.5
22
25
  type: :runtime
23
26
  prerelease: false
24
- version_requirements: *84224510
27
+ version_requirements: !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - <
31
+ - !ruby/object:Gem::Version
32
+ version: '3.0'
33
+ - - ! '>='
34
+ - !ruby/object:Gem::Version
35
+ version: 2.3.5
25
36
  description: ar_pg_array includes support of PostgreSQL's int[], float[], text[],
26
37
  timestamptz[] etc. into ActiveRecord. You could define migrations for array columns,
27
38
  query on array columns.
@@ -38,12 +49,10 @@ files:
38
49
  - init.rb
39
50
  - lib/ar_pg_array.rb
40
51
  - lib/ar_pg_array/allways_save.rb
52
+ - lib/ar_pg_array/parser.rb
41
53
  - lib/ar_pg_array/querying.rb
42
- - lib/ar_pg_array/querying_arel.rb
43
54
  - lib/ar_pg_array/references_by.rb
44
55
  - lib/ar_pg_array/schema.rb
45
- - lib/ar_pg_array/schema_arel.rb
46
- - lib/ar_pg_array/schema_cachable.rb
47
56
  - lib/ar_pg_array/schema_cacheable.rb
48
57
  - lib/ar_pg_array/schema_fix_will_change.rb
49
58
  - spec/fixtures/bulk.rb
@@ -53,6 +62,8 @@ files:
53
62
  - spec/fixtures/schema.rb
54
63
  - spec/fixtures/tag.rb
55
64
  - spec/fixtures/tags.yml
65
+ - spec/fixtures/unrelated.rb
66
+ - spec/fixtures/unrelateds.yml
56
67
  - spec/pg_array_spec.rb
57
68
  - spec/spec.opts
58
69
  - spec/spec_helper.rb
@@ -76,7 +87,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
76
87
  version: '0'
77
88
  requirements: []
78
89
  rubyforge_project: ar-pg-array
79
- rubygems_version: 1.8.12
90
+ rubygems_version: 1.8.24
80
91
  signing_key:
81
92
  specification_version: 3
82
93
  summary: Use power of PostgreSQL Arrays in ActiveRecord
@@ -1,234 +0,0 @@
1
- if Arel::VERSION >= '2.0'
2
- module Arel
3
- module Nodes
4
- class ArrayAny < Arel::Nodes::Binary
5
- end
6
-
7
- class ArrayAll < Arel::Nodes::Binary
8
- end
9
-
10
- class ArrayIncluded < Arel::Nodes::Binary
11
- end
12
- end
13
-
14
- module Predications
15
- def ar_any other
16
- Nodes::ArrayAny.new self, other
17
- end
18
-
19
- def ar_all other
20
- Nodes::ArrayAll.new self, other
21
- end
22
-
23
- def ar_included other
24
- Nodes::ArrayIncluded.new self, other
25
- end
26
- end
27
-
28
- module Visitors
29
- class PostgreSQL
30
- def visit_Arel_Nodes_ArrayAny o
31
- "#{visit o.left} && #{visit o.right}"
32
- end
33
-
34
- def visit_Arel_Nodes_ArrayAll o
35
- "#{visit o.left} @> #{visit o.right}"
36
- end
37
-
38
- def visit_Arel_Nodes_ArrayIncluded o
39
- "#{visit o.left} <@ #{visit o.right}"
40
- end
41
-
42
- def visit_PGArrays_PgArray o
43
- @connection.quote_array_for_arel_by_base_type(o, o.base_type)
44
- end
45
-
46
- alias :visit_PGArrays_PgAny :visit_PGArrays_PgArray
47
- alias :visit_PGArrays_PgAll :visit_PGArrays_PgArray
48
- alias :visit_PGArrays_PgIncluded :visit_PGArrays_PgArray
49
- end
50
- end
51
- end
52
- else
53
- module Arel
54
- module Predicates
55
- class ArrayAny < Binary
56
- def eval(row)
57
- !(operand1.eval(row) & operand2.eval(row)).empty?
58
- end
59
-
60
- def predicate_sql
61
- "&&"
62
- end
63
- end
64
-
65
- class ArrayAll < Binary
66
- def eval(row)
67
- (operand2.eval(row) - operand1.eval(row)).empty?
68
- end
69
-
70
- def predicate_sql
71
- "@>"
72
- end
73
- end
74
-
75
- class ArrayIncluded < Binary
76
- def eval(row)
77
- (operand1.eval(row) - operand2.eval(row)).empty?
78
- end
79
-
80
- def predicate_sql
81
- "<@"
82
- end
83
- end
84
- end
85
-
86
- class Attribute
87
- methods = lambda do
88
- def ar_any(other)
89
- Predicates::ArrayAny.new(self, other)
90
- end
91
-
92
- def ar_all(other)
93
- Predicates::ArrayAll.new(self, other)
94
- end
95
-
96
- def ar_included(other)
97
- Predicates::ArrayIncluded.new(self, other)
98
- end
99
- end
100
- if defined? PREDICATES
101
- PREDICATES.concat [:ar_any, :ar_all, :ar_included]
102
- class_exec &methods
103
- else
104
- Predications.class_exec &methods
105
- end
106
- end
107
- end
108
-
109
- module PGArrays
110
- class PgArray
111
- def to_sql( formatter = nil )
112
- formatter.engine.connection.quote_array_for_arel_by_base_type(self, base_type)
113
- end
114
-
115
- def to_a
116
- self
117
- end
118
- end
119
- end
120
- end
121
-
122
- if ActiveRecord::VERSION::STRING < '3.1'
123
- module ActiveRecord
124
- class PredicateBuilder
125
- def build_from_hash(attributes, default_table)
126
- predicates = attributes.map do |column, value|
127
- table = default_table
128
-
129
- if value.is_a?(Hash)
130
- table = Arel::Table.new(column, :engine => @engine)
131
- build_from_hash(value, table)
132
- else
133
- column = column.to_s
134
-
135
- if column.include?('.')
136
- table_name, column = column.split('.', 2)
137
- table = Arel::Table.new(table_name, :engine => @engine)
138
- end
139
-
140
- attribute = table[column] || Arel::Attribute.new(table, column)
141
-
142
- case value
143
- when PGArrays::PgAny
144
- attribute.ar_any(value)
145
- when PGArrays::PgAll
146
- attribute.ar_all(value)
147
- when PGArrays::PgIncludes
148
- attribute.ar_included(value)
149
- when Array, ActiveRecord::Associations::AssociationCollection, ActiveRecord::Relation
150
- values = value.to_a.map { |x|
151
- x.is_a?(ActiveRecord::Base) ? x.id : x
152
- }
153
- attribute.in(values)
154
- when Range, Arel::Relation
155
- attribute.in(value)
156
- when ActiveRecord::Base
157
- attribute.eq(value.id)
158
- when Class
159
- # FIXME: I think we need to deprecate this behavior
160
- attribute.eq(value.name)
161
- else
162
- attribute.eq(value)
163
- end
164
- end
165
- end
166
-
167
- predicates.flatten
168
- end
169
- end
170
- end
171
- else
172
- module ActiveRecord
173
- class PredicateBuilder
174
- def self.build_from_hash(engine, attributes, default_table)
175
- predicates = attributes.map do |column, value|
176
- table = default_table
177
-
178
- if value.is_a?(Hash)
179
- table = Arel::Table.new(column, engine)
180
- build_from_hash(engine, value, table)
181
- else
182
- column = column.to_s
183
-
184
- if column.include?('.')
185
- table_name, column = column.split('.', 2)
186
- table = Arel::Table.new(table_name, engine)
187
- end
188
-
189
- attribute = table[column.to_sym]
190
-
191
- case value
192
- when PGArrays::PgAny
193
- attribute.ar_any(value)
194
- when PGArrays::PgAll
195
- attribute.ar_all(value)
196
- when PGArrays::PgIncludes
197
- attribute.ar_included(value)
198
- when ActiveRecord::Relation
199
- value = value.select(value.klass.arel_table[value.klass.primary_key]) if value.select_values.empty?
200
- attribute.in(value.arel.ast)
201
- when Array, ActiveRecord::Associations::CollectionProxy
202
- values = value.to_a.map { |x|
203
- x.is_a?(ActiveRecord::Base) ? x.id : x
204
- }
205
-
206
- if values.include?(nil)
207
- values = values.compact
208
- if values.empty?
209
- attribute.eq nil
210
- else
211
- attribute.in(values.compact).or attribute.eq(nil)
212
- end
213
- else
214
- attribute.in(values)
215
- end
216
-
217
- when Range, Arel::Relation
218
- attribute.in(value)
219
- when ActiveRecord::Base
220
- attribute.eq(value.id)
221
- when Class
222
- # FIXME: I think we need to deprecate this behavior
223
- attribute.eq(value.name)
224
- else
225
- attribute.eq(value)
226
- end
227
- end
228
- end
229
-
230
- predicates.flatten
231
- end
232
- end
233
- end
234
- end
@@ -1,105 +0,0 @@
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.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) && (value.acts_like?(:date) || value.acts_like?(:time))
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
- if Arel::VERSION < '2.0'
69
- module Sql
70
- module Attributes
71
- class << self
72
- def for_with_postgresql_arrays(column)
73
- if column.type.to_s =~ /^(.+)_array$/
74
- ('Arel::Sql::Attributes::' + for_without_postgresql_arrays(column.base_column).name.split('::').last + 'Array').constantize
75
- else
76
- for_without_postgresql_arrays(column)
77
- end
78
- end
79
- alias_method_chain :for, :postgresql_arrays
80
- end
81
-
82
- %w{Integer Float Decimal Boolean String Time}.each do |basetype|
83
- module_eval <<-"END"
84
- class #{basetype}Array < Arel::Attributes::#{basetype}Array
85
- include Attributes
86
- end
87
- END
88
- end
89
- end
90
- end
91
- else
92
- module Attributes
93
- class << self
94
- def for_with_postgresql_arrays(column)
95
- if column.type.to_s =~ /^(.+)_array$/
96
- ('Arel::Attributes::' + for_without_postgresql_arrays(column.base_column).name.split('::').last + 'Array').constantize
97
- else
98
- for_without_postgresql_arrays(column)
99
- end
100
- end
101
- alias_method_chain :for, :postgresql_arrays
102
- end
103
- end
104
- end
105
- end
@@ -1,11 +0,0 @@
1
- adjust_cached_types = lambda do |atcbd|
2
- atcbd << /_array$/
3
- def atcbd.include?(val)
4
- any?{|type| type === val}
5
- end
6
- end
7
- if ActiveRecord::VERSION::MAJOR < 3
8
- adjust_cached_types.call(ActiveRecord::AttributeMethods::ATTRIBUTE_TYPES_CACHED_BY_DEFAULT)
9
- else
10
- adjust_cached_types.call(ActiveRecord::AttributeMethods::Read::ATTRIBUTE_TYPES_CACHED_BY_DEFAULT)
11
- end