sunstone 5.0.0.beta3 → 5.0.0.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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.tm_properties +1 -0
  4. data/.travis.yml +36 -0
  5. data/README.md +1 -2
  6. data/Rakefile.rb +1 -1
  7. data/ext/active_record/associations/collection_association.rb +48 -6
  8. data/ext/active_record/attribute_methods.rb +25 -21
  9. data/ext/active_record/callbacks.rb +17 -0
  10. data/ext/active_record/finder_methods.rb +44 -2
  11. data/ext/active_record/persistence.rb +127 -1
  12. data/ext/active_record/relation.rb +13 -5
  13. data/ext/active_record/relation/calculations.rb +25 -0
  14. data/ext/active_record/statement_cache.rb +3 -2
  15. data/ext/active_record/transactions.rb +60 -0
  16. data/ext/arel/attributes/empty_relation.rb +31 -0
  17. data/ext/arel/attributes/relation.rb +3 -2
  18. data/lib/active_record/connection_adapters/sunstone/database_statements.rb +13 -2
  19. data/lib/active_record/connection_adapters/sunstone/schema_dumper.rb +16 -0
  20. data/lib/active_record/connection_adapters/sunstone/schema_statements.rb +2 -2
  21. data/lib/active_record/connection_adapters/sunstone/type/uuid.rb +21 -0
  22. data/lib/active_record/connection_adapters/sunstone_adapter.rb +54 -30
  23. data/lib/arel/collectors/sunstone.rb +6 -4
  24. data/lib/arel/visitors/sunstone.rb +61 -39
  25. data/lib/sunstone.rb +18 -11
  26. data/lib/sunstone/connection.rb +62 -22
  27. data/lib/sunstone/exception.rb +3 -0
  28. data/lib/sunstone/gis.rb +1 -0
  29. data/lib/sunstone/version.rb +2 -2
  30. data/sunstone.gemspec +4 -5
  31. data/test/active_record/associations/has_and_belongs_to_many_test.rb +12 -0
  32. data/test/active_record/associations/has_many_test.rb +72 -0
  33. data/test/active_record/eager_loading_test.rb +15 -0
  34. data/test/active_record/persistance_test.rb +190 -0
  35. data/test/active_record/preload_test.rb +16 -0
  36. data/test/active_record/query_test.rb +91 -0
  37. data/test/models.rb +91 -0
  38. data/test/sunstone/connection/configuration_test.rb +44 -0
  39. data/test/sunstone/connection/cookie_store_test.rb +37 -0
  40. data/test/sunstone/connection/request_helper_test.rb +105 -0
  41. data/test/sunstone/connection/send_request_test.rb +164 -0
  42. data/test/sunstone/connection_test.rb +2 -298
  43. data/test/test_helper.rb +45 -2
  44. metadata +52 -47
  45. data/ext/active_record/associations/builder/has_and_belongs_to_many.rb +0 -48
  46. data/ext/active_record/calculations.rb +0 -32
  47. data/ext/active_record/query_methods.rb +0 -30
  48. data/ext/active_record/relation/predicate_builder.rb +0 -23
  49. data/test/models/ship.rb +0 -14
  50. data/test/query_test.rb +0 -134
  51. data/test/sunstone/parser_test.rb +0 -124
  52. data/test/sunstone_test.rb +0 -303
@@ -5,16 +5,24 @@ module ActiveRecord
5
5
  @to_sql ||= begin
6
6
  relation = self
7
7
  connection = klass.connection
8
- visitor = connection.visitor.is_a?(Arel::Visitors::Sunstone) ? Arel::Visitors::ToSql.new(connection) : connection.visitor
8
+ visitor = if connection.visitor.is_a?(Arel::Visitors::Sunstone)
9
+ Arel::Visitors::ToSql.new(connection)
10
+ else
11
+ connection.visitor
12
+ end
9
13
 
10
14
  if eager_loading?
11
15
  find_with_associations { |rel| relation = rel }
12
16
  end
13
17
 
14
- arel = relation.arel
15
- binds = connection.prepare_binds_for_database(arel.bind_values + relation.bound_attributes)
16
- binds.map! { |bv| connection.quote(bv) }
17
- collect = visitor.accept(arel.ast, Arel::Collectors::Bind.new)
18
+ binds = if connection.visitor.is_a?(Arel::Visitors::Sunstone)
19
+ relation.arel.bind_values + relation.bound_attributes
20
+ else
21
+ relation.bound_attributes
22
+ end
23
+ binds = connection.prepare_binds_for_database(binds)
24
+ binds.map! { |value| connection.quote(value) }
25
+ collect = visitor.accept(relation.arel.ast, Arel::Collectors::Bind.new)
18
26
  collect.substitute_binds(binds).join
19
27
  end
20
28
  end
@@ -0,0 +1,25 @@
1
+ module ActiveRecord
2
+ module Calculations
3
+
4
+ def pluck(*column_names)
5
+ if loaded? && (column_names.map(&:to_s) - @klass.attribute_names - @klass.attribute_aliases.keys).empty?
6
+ return @records.pluck(*column_names)
7
+ end
8
+
9
+ if has_include?(column_names.first)
10
+ construct_relation_for_association_calculations.pluck(*column_names)
11
+ elsif klass.connection.is_a?(ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter)
12
+ load
13
+ return @records.pluck(*column_names.map{|n| n.sub(/^#{klass.table_name}\./, "")})
14
+ else
15
+ relation = spawn
16
+ relation.select_values = column_names.map { |cn|
17
+ @klass.has_attribute?(cn) || @klass.attribute_alias?(cn) ? arel_attribute(cn) : cn
18
+ }
19
+ result = klass.connection.select_all(relation.arel, nil, bound_attributes)
20
+ result.cast_values(klass.attribute_types)
21
+ end
22
+ end
23
+
24
+ end
25
+ end
@@ -22,7 +22,7 @@ module ActiveRecord
22
22
  @values.compile(binds)
23
23
  else
24
24
  val = @values.dup
25
- @indexes.each { |i| val[i] = connection.quote(*binds.shift.reverse) }
25
+ @indexes.each { |i| val[i] = connection.quote(binds.shift) }
26
26
  val.join
27
27
  end
28
28
  end
@@ -30,7 +30,8 @@ module ActiveRecord
30
30
 
31
31
  def self.partial_query(visitor, ast, collector)
32
32
  collected = visitor.accept(ast, collector)
33
- PartialQuery.new(visitor.is_a?(Arel::Visitors::Sunstone) ? collected : collected.value, visitor.is_a?(Arel::Visitors::Sunstone))
33
+ collected = collected.value if !visitor.is_a?(Arel::Visitors::Sunstone)
34
+ PartialQuery.new(collected, visitor.is_a?(Arel::Visitors::Sunstone))
34
35
  end
35
36
 
36
37
  end
@@ -0,0 +1,60 @@
1
+ module ActiveRecord
2
+ # See ActiveRecord::Transactions::ClassMethods for documentation.
3
+ module Transactions
4
+
5
+ # # See ActiveRecord::Transactions::ClassMethods for detailed documentation.
6
+ # def transaction(options = {}, &block)
7
+ # self.class.transaction(options, &block)
8
+ # end
9
+ #
10
+ # def destroy #:nodoc:
11
+ # with_transaction_returning_status { super }
12
+ # end
13
+ #
14
+ # def save(*) #:nodoc:
15
+ # rollback_active_record_state! do
16
+ # with_transaction_returning_status { super }
17
+ # end
18
+ # end
19
+ #
20
+ # def save!(*) #:nodoc:
21
+ # with_transaction_returning_status { super }
22
+ # end
23
+ #
24
+ # def touch(*) #:nodoc:
25
+ # with_transaction_returning_status { super }
26
+ # end
27
+
28
+ def with_transaction_returning_status
29
+ if self.class.connection.is_a?(ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter) && @updating
30
+ begin
31
+ status = yield
32
+ rescue ActiveRecord::Rollback
33
+ clear_transaction_record_state
34
+ status = nil
35
+ end
36
+ return status
37
+ end
38
+
39
+ status = nil
40
+ self.class.transaction do
41
+ add_to_transaction
42
+ begin
43
+ status = yield
44
+ rescue ActiveRecord::Rollback
45
+ clear_transaction_record_state
46
+ status = nil
47
+ end
48
+
49
+ raise ActiveRecord::Rollback unless status
50
+ end
51
+ status
52
+ ensure
53
+ if @transaction_state && @transaction_state.committed?
54
+ clear_transaction_record_state
55
+ end
56
+ end
57
+
58
+
59
+ end
60
+ end
@@ -0,0 +1,31 @@
1
+ module Arel
2
+ module Attributes
3
+ class EmptyRelation < Attribute
4
+
5
+ attr_accessor :collection, :for_write
6
+
7
+ def initialize(relation, name, collection = false, for_write=false)
8
+ self[:relation] = relation
9
+ self[:name] = name
10
+ @collection = collection
11
+ @for_write = for_write
12
+ end
13
+
14
+ def able_to_type_cast?
15
+ false
16
+ end
17
+
18
+ def table_name
19
+ nil
20
+ end
21
+
22
+ def eql? other
23
+ self.class == other.class &&
24
+ self.relation == other.relation &&
25
+ self.name == other.name &&
26
+ self.collection == other.collection
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -2,12 +2,13 @@ module Arel
2
2
  module Attributes
3
3
  class Relation < Attribute
4
4
 
5
- attr_accessor :collection
5
+ attr_accessor :collection, :for_write
6
6
 
7
- def initialize(relation, name, collection = false)
7
+ def initialize(relation, name, collection = false, for_write=false)
8
8
  self[:relation] = relation
9
9
  self[:name] = name
10
10
  @collection = collection
11
+ @for_write = for_write
11
12
  end
12
13
 
13
14
  def able_to_type_cast?
@@ -15,13 +15,24 @@ module ActiveRecord
15
15
 
16
16
  def exec_query(arel, name = 'SAR', binds = [], prepare: false)
17
17
  sar = to_sar(arel, binds)
18
- result = exec(sar, name)
18
+
19
+ log_mess = sar.path.split('?', 2)
20
+ result = log("#{sar.method} #{log_mess[0]} #{(log_mess[1] && !log_mess[1].empty?) ? MessagePack.unpack(CGI.unescape(log_mess[1])) : '' }", name) {
21
+ response = @connection.send_request(sar)
22
+ if response.is_a?(Net::HTTPNoContent)
23
+ nil
24
+ else
25
+ JSON.parse(response.body)
26
+ end
27
+ }
19
28
 
20
29
  if sar.instance_variable_get(:@sunstone_calculation)
21
30
  # this is a count, min, max.... yea i know..
22
31
  ActiveRecord::Result.new(['all'], [result], {:all => type_map.lookup('integer')})
23
- else
32
+ elsif result.is_a?(Array)
24
33
  ActiveRecord::Result.new(result[0] ? result[0].keys : [], result.map{|r| r.values})
34
+ else
35
+ ActiveRecord::Result.new(result.keys, [result])
25
36
  end
26
37
  end
27
38
 
@@ -0,0 +1,16 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module Sunstone
4
+ module ColumnDumper
5
+
6
+ # Adds +:array+ option to the default set
7
+ def prepare_column_options(column)
8
+ spec = super
9
+ spec[:array] = 'true' if column.array?
10
+ spec
11
+ end
12
+
13
+ end
14
+ end
15
+ end
16
+ end
@@ -25,13 +25,13 @@ module ActiveRecord
25
25
  # - format_type includes the column size constraint, e.g. varchar(50)
26
26
  # - ::regclass is a function that gives the id for a table name
27
27
  def column_definitions(table_name) # :nodoc:
28
- Wankel.parse(@connection.get("/#{table_name}/schema").body)
28
+ JSON.parse(@connection.get("/#{table_name}/schema").body)
29
29
  rescue ::Sunstone::Exception::NotFound
30
30
  raise ActiveRecord::StatementInvalid, "Table \"#{table_name}\" does not exist"
31
31
  end
32
32
 
33
33
  def tables
34
- Wankel.parse(@connection.get('/tables').body)
34
+ JSON.parse(@connection.get('/tables').body)
35
35
  end
36
36
 
37
37
  def views
@@ -0,0 +1,21 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module Sunstone
4
+ module Type # :nodoc:
5
+ class Uuid < ActiveRecord::Type::Value # :nodoc:
6
+ ACCEPTABLE_UUID = %r{\A\{?([a-fA-F0-9]{4}-?){8}\}?\z}x
7
+
8
+ alias_method :serialize, :deserialize
9
+
10
+ def type
11
+ :uuid
12
+ end
13
+
14
+ def cast(value)
15
+ value.to_s[ACCEPTABLE_UUID, 0]
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -1,19 +1,20 @@
1
1
  require 'active_record/connection_adapters/abstract_adapter'
2
- require 'active_record/connection_adapters/statement_pool'
3
2
 
3
+ #require 'active_record/connection_adapters/statement_pool'
4
4
 
5
5
  require 'active_record/connection_adapters/sunstone/database_statements'
6
6
  require 'active_record/connection_adapters/sunstone/schema_statements'
7
+ require 'active_record/connection_adapters/sunstone/schema_dumper'
7
8
  require 'active_record/connection_adapters/sunstone/column'
8
9
 
9
10
  require 'active_record/connection_adapters/sunstone/type/date_time'
10
11
  require 'active_record/connection_adapters/sunstone/type/array'
11
- require 'active_record/connection_adapters/sunstone/type/ewkb'
12
+ require 'active_record/connection_adapters/sunstone/type/uuid'
12
13
 
13
14
  module ActiveRecord
14
15
  module ConnectionHandling # :nodoc:
15
16
 
16
- VALID_SUNSTONE_CONN_PARAMS = [:site, :host, :port, :api_key, :use_ssl, :user_agent, :ca_cert]
17
+ VALID_SUNSTONE_CONN_PARAMS = [:url, :host, :port, :api_key, :use_ssl, :user_agent, :ca_cert]
17
18
 
18
19
  # Establishes a connection to the database that's used by all Active Record
19
20
  # objects
@@ -25,8 +26,8 @@ module ActiveRecord
25
26
  # Map ActiveRecords param names to PGs.
26
27
  conn_params[:user] = conn_params.delete(:username) if conn_params[:username]
27
28
  conn_params[:dbname] = conn_params.delete(:database) if conn_params[:database]
28
- if conn_params[:site]
29
- uri = URI.parse(conn_params.delete(:site))
29
+ if conn_params[:url]
30
+ uri = URI.parse(conn_params.delete(:url))
30
31
  conn_params[:api_key] ||= (uri.user ? CGI.unescape(uri.user) : nil)
31
32
  conn_params[:host] ||= uri.host
32
33
  conn_params[:port] ||= uri.port
@@ -67,7 +68,7 @@ module ActiveRecord
67
68
  # include PostgreSQL::ReferentialIntegrity
68
69
  include Sunstone::SchemaStatements
69
70
  include Sunstone::DatabaseStatements
70
- # include PostgreSQL::ColumnDumper
71
+ include Sunstone::ColumnDumper
71
72
  # include Savepoints
72
73
 
73
74
  # Returns 'SunstoneAPI' as adapter name for identification purposes.
@@ -75,17 +76,11 @@ module ActiveRecord
75
76
  ADAPTER_NAME
76
77
  end
77
78
 
78
- # Adds `:array` option to the default set provided by the AbstractAdapter
79
- def prepare_column_options(column, types) # :nodoc:
80
- spec = super
81
- spec[:array] = 'true' if column.respond_to?(:array) && column.array
82
- spec
83
- end
84
-
85
79
  # Initializes and connects a SunstoneAPI adapter.
86
80
  def initialize(connection, logger, connection_parameters, config)
87
81
  super(connection, logger, config)
88
82
 
83
+ @prepared_statements = false
89
84
  @visitor = Arel::Visitors::Sunstone.new
90
85
  @connection_parameters, @config = connection_parameters, config
91
86
 
@@ -151,7 +146,7 @@ module ActiveRecord
151
146
  end
152
147
 
153
148
  def server_config
154
- Wankel.parse(@connection.get("/configuration").body)
149
+ JSON.parse(@connection.get("/configuration").body)
155
150
  end
156
151
 
157
152
  def lookup_cast_type_from_column(column) # :nodoc:
@@ -162,7 +157,47 @@ module ActiveRecord
162
157
  end
163
158
  end
164
159
 
160
+ def transaction(requires_new: nil, isolation: nil, joinable: true)
161
+ Thread.current[:sunstone_transaction_count] ||= 0
162
+ Thread.current[:sunstone_request_sent] = nil if Thread.current[:sunstone_transaction_count] == 0
163
+ Thread.current[:sunstone_transaction_count] += 1
164
+ begin
165
+ yield
166
+ ensure
167
+ Thread.current[:sunstone_transaction_count] -= 1
168
+ if Thread.current[:sunstone_transaction_count] == 0
169
+ Thread.current[:sunstone_transaction_count] = nil
170
+ Thread.current[:sunstone_request_sent] = nil
171
+ end
172
+ end
173
+ rescue ActiveRecord::Rollback
174
+ # rollbacks are silently swallowed
175
+ end
176
+
177
+ def supports_json?
178
+ true
179
+ end
165
180
 
181
+ # Executes an INSERT query and returns the new record's ID
182
+ #
183
+ # +id_value+ will be returned unless the value is nil, in
184
+ # which case the database will attempt to calculate the last inserted
185
+ # id and return that value.
186
+ #
187
+ # If the next id was calculated in advance (as in Oracle), it should be
188
+ # passed in as +id_value+.
189
+ def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
190
+ sql, binds, pk, sequence_name = sql_for_insert(to_sql(arel, binds), pk, id_value, sequence_name, binds)
191
+ value = exec_insert(sql, name, binds, pk, sequence_name)
192
+ end
193
+ alias create insert
194
+
195
+ # Should be the defuat insert, but rails escapes if for SQL so we'll just
196
+ # catch the string "DEFATUL VALUES" in the visitor
197
+ # def empty_insert_statement_value
198
+ # {}
199
+ # end
200
+
166
201
  private
167
202
 
168
203
  def initialize_type_map(m) # :nodoc:
@@ -172,22 +207,11 @@ module ActiveRecord
172
207
  m.register_type 'decimal', Type::Decimal.new
173
208
  m.register_type 'datetime', Sunstone::Type::DateTime.new
174
209
  m.register_type 'json', Type::Internal::AbstractJson.new
175
- m.register_type 'ewkb', Sunstone::Type::EWKB.new
176
- end
177
-
178
- def exec(arel, name='SAR', binds=[])
179
- # result = without_prepared_statement?(binds) ? exec_no_cache(sql, name, binds) :
180
- # exec_cache(sql, name, binds)
181
- sar = to_sar(arel, binds)
182
-
183
- log(sar.is_a?(String) ? sar : "#{sar.class} #{CGI.unescape(sar.path)}", name) {
184
- response = @connection.send_request(sar)
185
- if response.is_a?(Net::HTTPNoContent)
186
- nil
187
- else
188
- Wankel.parse(response.body)
189
- end
190
- }
210
+ m.register_type 'uuid', Sunstone::Type::Uuid.new
211
+
212
+ if defined?(Sunstone::Type::EWKB)
213
+ m.register_type 'ewkb', Sunstone::Type::EWKB.new
214
+ end
191
215
  end
192
216
 
193
217
  # Connects to a Sunstone API server and sets up the adapter depending on
@@ -89,13 +89,15 @@ module Arel
89
89
  path += "/#{get_params[:where]['id']}"
90
90
  get_params.delete(:where)
91
91
  end
92
-
93
92
  if get_params.size > 0
94
- path += '?m=' + URI.escape(CGI.escape(MessagePack.pack(get_params)))
93
+ path += "?#{CGI.escape(MessagePack.pack(get_params))}"
95
94
  end
96
-
95
+
97
96
  request = request_type.new(path)
98
- request.instance_variable_set(:@sunstone_calculation, true) if [:calculate, :update, :delete, :insert].include?(operation)
97
+ if get_params.size > 0
98
+ request['Query-Encoding'] = 'application/msgpack'
99
+ end
100
+ request.instance_variable_set(:@sunstone_calculation, true) if [:calculate, :delete].include?(operation)
99
101
 
100
102
  if updates
101
103
  request.body = body
@@ -78,30 +78,35 @@ module Arel
78
78
  collector.operation = :insert
79
79
 
80
80
  if o.values
81
- keys = o.values.right.map { |x| visit(x, collector) }
82
- values = o.values.left
83
- collector.updates = {}
81
+
82
+ if o.values.is_a?(Arel::Nodes::SqlLiteral) && o.values == 'DEFAULT VALUES'
83
+ collector.updates = {}
84
+ else
85
+ keys = o.values.right.map { |x| visit(x, collector) }
86
+ values = o.values.left
87
+ collector.updates = {}
84
88
 
85
89
 
86
- keys.each_with_index do |k, i|
87
- if k.is_a?(Hash)
88
- add_to_bottom_of_hash_or_array(k, values[i])
89
- collector.updates.deep_merge!(k) { |key, v1, v2|
90
- if (v1.is_a?(Array) && v2.is_a?(Array))
91
- v2.each_with_index do |v, i|
92
- if v1[i].nil?
93
- v1[i] = v2[i]
94
- else
95
- v1[i].deep_merge!(v2[i]) unless v2[i].nil?
90
+ keys.each_with_index do |k, i|
91
+ if k.is_a?(Hash)
92
+ add_to_bottom_of_hash_or_array(k, values[i])
93
+ collector.updates.deep_merge!(k) { |key, v1, v2|
94
+ if (v1.is_a?(Array) && v2.is_a?(Array))
95
+ v2.each_with_index do |v, j|
96
+ if v1[j].nil?
97
+ v1[j] = v2[j]
98
+ else
99
+ v1[j].deep_merge!(v2[j]) unless v2[j].nil?
100
+ end
96
101
  end
102
+ v1
103
+ else
104
+ v2
97
105
  end
98
- v1
99
- else
100
- v2
101
- end
102
- }
103
- else
104
- collector.updates[k] = values[i]
106
+ }
107
+ else
108
+ collector.updates[k] = values[i]
109
+ end
105
110
  end
106
111
  end
107
112
  end
@@ -198,9 +203,9 @@ module Arel
198
203
  value.each do |key, v|
199
204
  if key.is_a?(Hash)
200
205
  add_to_bottom_of_hash_or_array(key, v)
201
- collector.updates.deep_merge!(key) { |key, v1, v2|
206
+ collector.updates.deep_merge!(key) { |k, v1, v2|
202
207
  if (v1.is_a?(Array) && v2.is_a?(Array))
203
- v2.each_with_index do |v, i|
208
+ v2.each_with_index do |v2k, i|
204
209
  if v1[i].nil?
205
210
  v1[i] = v2[i]
206
211
  else
@@ -689,18 +694,13 @@ module Arel
689
694
  visit(o.left, collector) => {in: visit(o.right, collector)}
690
695
  }
691
696
  end
692
- #
693
- # def visit_Arel_Nodes_NotIn o, collector
694
- # if Array === o.right && o.right.empty?
695
- # collector << '1=1'
696
- # else
697
- # collector = visit o.left, collector
698
- # collector << " NOT IN ("
699
- # collector = visit o.right, collector
700
- # collector << ")"
701
- # end
702
- # end
703
- #
697
+
698
+ def visit_Arel_Nodes_NotIn o, collector
699
+ {
700
+ visit(o.left, collector) => {not_in: visit(o.right, collector)}
701
+ }
702
+ end
703
+
704
704
  def visit_Arel_Nodes_And o, collector
705
705
  ors = []
706
706
  ors << o.children.inject({}) do |c, x|
@@ -865,20 +865,42 @@ module Arel
865
865
  end
866
866
  end
867
867
 
868
- def visit_Arel_Attributes_Relation o, collector
868
+ def visit_Arel_Attributes_Relation o, collector, top=true
869
+ value = if o.relation.is_a?(Arel::Attributes::Relation)
870
+ visit_Arel_Attributes_Relation(o.relation, collector, false)
871
+ else
872
+ visit(o.relation, collector)
873
+ end
874
+ value = value.to_s.split('.').last if !value.is_a?(Hash)
875
+
869
876
  if o.collection
870
877
  ary = []
871
- ary[o.collection] = visit(o.relation, collector).to_s.split('.').last
872
- {"#{o.name}_attributes" => ary}
878
+ ary[o.collection] = value
879
+ if top && o.name == collector.table
880
+ ary
881
+ elsif o.for_write
882
+ {"#{o.name}_attributes" => ary}
883
+ else
884
+ {o.name => ary}
885
+ end
873
886
  else
874
- {"#{o.name}_attributes" => visit(o.relation, collector).to_s.split('.').last}
887
+ if top && o.name == collector.table
888
+ value
889
+ elsif o.for_write
890
+ {"#{o.name}_attributes" => value}
891
+ else
892
+ {o.name => value}
893
+ end
875
894
  end
876
895
  end
896
+
897
+ def visit_Arel_Attributes_EmptyRelation o, collector, top=true
898
+ o.for_write ? "#{o.name}_attributes" : o.name
899
+ end
877
900
 
878
901
  def visit_Arel_Attributes_Attribute o, collector
879
902
  join_name = o.relation.table_alias || o.relation.name
880
903
  collector.table == join_name ? o.name : "#{join_name}.#{o.name}"
881
- # o.name
882
904
  end
883
905
  alias :visit_Arel_Attributes_Integer :visit_Arel_Attributes_Attribute
884
906
  alias :visit_Arel_Attributes_Float :visit_Arel_Attributes_Attribute