ar_pg_array 0.9.13 → 0.10.1

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/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