sunstone 5.0.0.beta3 → 5.0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c5c130a7d5b7307e472bd22688ee0b8621db478e
4
- data.tar.gz: a3fde9dce1b08795ea7ff9a1217921fb57c099ef
3
+ metadata.gz: a1fc21e0215013059317cc971ba06e8a221343e2
4
+ data.tar.gz: 9c9f8128129d8d9ff50e398f7982f9288573a41f
5
5
  SHA512:
6
- metadata.gz: 383524737f0c0fefbec2c99d62d35ce23c6e3060f772e11ebac65418493cf2ee892e34cd637a6f86bbf5439275103c986887f4dd05494cb223a8cc51a9b67349
7
- data.tar.gz: df9fe4e2e39011303804a12fe26b49b99c84c3651c017a202fbcec907b8439708f4ae04ca72400c47cc5031687de9c179495e23a5644a71ec6666ff69bcd1590
6
+ metadata.gz: ccb44a14900ab3434926246a341740026723695e868283a0d09f3b4d6e41700da0aaf2f77f61c4bc69ca6fe3cdeee5204924fc67695b5ddcc09c211ae4a466a4
7
+ data.tar.gz: 2fb90af0945f9c7ae8dbd1dad2dc7c0072efa24dcd2bd3cf1b76507fb6754e4a06f6093117d2e94c5900624f7083effdcaab8d02a00ff648c28e268fa2fc46b7
data/.gitignore CHANGED
@@ -8,6 +8,7 @@
8
8
  /test/tmp/
9
9
  /test/version_tmp/
10
10
  /tmp/
11
+ .DS_Store
11
12
 
12
13
  ## Documentation cache and generated files:
13
14
  /.yardoc/
@@ -0,0 +1 @@
1
+ exclude = '{$exclude,log,bin,tmp,.tm_properties,coverage}'
@@ -0,0 +1,36 @@
1
+ language: ruby
2
+ rvm: 2.3.1
3
+
4
+ cache:
5
+ bundler: true
6
+ directories:
7
+ - /home/travis/.rvm/gems
8
+
9
+ addons:
10
+ postgresql: "9.4"
11
+
12
+ before_install:
13
+ - unset BUNDLE_GEMFILE
14
+
15
+ install:
16
+ - git clone https://github.com/rails/rails.git ~/build/rails
17
+
18
+ before_script:
19
+ - export RAILS_VERSION=`cat sunstone.gemspec | grep activerecord | grep -ow "[0-9\.]\{1,\}"`
20
+ - pushd ~/build/rails
21
+ - git checkout v$RAILS_VERSION
22
+ - sed -i "/require 'support\/connection'/a \$LOAD_PATH.unshift\(File.expand_path\('~\/build\/malomalo\/sunstone\/lib'\)\)\nrequire 'sunstone'" ~/build/rails/activerecord/test/cases/helper.rb
23
+ - cat ~/build/rails/Gemfile
24
+ - "sed -i \"/group :db do/a gem 'sunstone', path: File.expand_path\\('~\\/build\\/malomalo\\/sunstone'\\)\" ~/build/rails/Gemfile"
25
+ - cat ~/build/rails/Gemfile
26
+ - bundle install --jobs=3 --retry=3
27
+ - createdb activerecord_unittest
28
+ - createdb activerecord_unittest2
29
+ - mysql -e "create database IF NOT EXISTS activerecord_unittest DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci;"
30
+ - mysql -e "create database IF NOT EXISTS activerecord_unittest2 DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci;"
31
+ - gem environment gempath
32
+ - popd
33
+ - bundle install --jobs=3 --retry=3
34
+ - gem environment gempath
35
+
36
+ script: bundle exec rake test && cd ~/build/rails/activerecord && bundle exec rake test --verbose
data/README.md CHANGED
@@ -1,5 +1,4 @@
1
- Sunstone
2
- ========
1
+ # Sunstone [![Travis CI](https://travis-ci.org/malomalo/sunstone.svg)](https://travis-ci.org/malomalo/sunstone)
3
2
 
4
3
  An [ActiveRecord](https://rubygems.org/gems/activerecord) adapter for quering
5
4
  APIs over Standard API (https://github.com/waratuman/standardapi).
@@ -12,7 +12,7 @@ task :c => :console
12
12
  Rake::TestTask.new do |t|
13
13
  t.libs << 'test'
14
14
  t.test_files = FileList['test/**/*_test.rb']
15
- #t.warning = true
15
+ t.warning = false
16
16
  #t.verbose = true
17
17
  end
18
18
 
@@ -6,12 +6,22 @@ module ActiveRecord
6
6
  other_array.each { |val| raise_on_type_mismatch!(val) }
7
7
  original_target = load_target.dup
8
8
 
9
- if owner.instance_variable_get(:@updateing)
9
+ if owner.new_record?
10
+ replace_records(other_array, original_target)
11
+ elsif owner.class.connection.is_a?(ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter) && owner.instance_variable_defined?(:@updating) && owner.instance_variable_get(:@updating)
10
12
  replace_common_records_in_memory(other_array, original_target)
11
- concat(other_array - original_target)
13
+
14
+ # Remove from target
15
+ (original_target - other_array).each { |record| callback(:before_remove, record) }
16
+ (original_target - other_array).each { |record| target.delete(record) }
17
+ (original_target - other_array).each { |record| callback(:after_remove, record) }
18
+
19
+ # Add to target
20
+ (other_array - original_target).each do |record|
21
+ add_to_target(record)
22
+ end
23
+
12
24
  other_array
13
- elsif owner.new_record?
14
- replace_records(other_array, original_target)
15
25
  else
16
26
  replace_common_records_in_memory(other_array, original_target)
17
27
  if other_array != original_target
@@ -22,17 +32,47 @@ module ActiveRecord
22
32
  end
23
33
  end
24
34
 
35
+
36
+ end
37
+
38
+ class HasManyAssociation
39
+
40
+ def insert_record(record, validate = true, raise = false)
41
+ set_owner_attributes(record)
42
+ set_inverse_instance(record)
43
+
44
+ if record.class.connection.is_a?(ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter) && (!owner.instance_variable_defined?(:@updating) && owner.instance_variable_get(:@updating))
45
+ true
46
+ elsif raise
47
+ record.save!(:validate => validate)
48
+ else
49
+ record.save(:validate => validate)
50
+ end
51
+ end
52
+
53
+ private
54
+ def save_through_record(record)
55
+ return if record.class.connection.is_a?(ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter)
56
+ build_through_record(record).save!
57
+ ensure
58
+ @through_records.delete(record.object_id)
59
+ end
60
+
25
61
  end
62
+
26
63
  end
27
64
  end
28
65
 
29
66
  module ActiveRecord
30
67
  module Persistence
68
+
31
69
  # Updates the attributes of the model from the passed-in hash and saves the
32
70
  # record, all wrapped in a transaction. If the object is invalid, the saving
33
71
  # will fail and false will be returned.
34
72
  def update(attributes)
35
- @updateing = true
73
+ @updating = :updating
74
+ $updating_model = self
75
+
36
76
  # The following transaction covers any possible database side-effects of the
37
77
  # attributes assignment. For example, setting the IDs of a child collection.
38
78
  with_transaction_returning_status do
@@ -40,7 +80,9 @@ module ActiveRecord
40
80
  save
41
81
  end
42
82
  ensure
43
- @updateing = false
83
+ @updating = false
84
+ $updating_model = nil
44
85
  end
86
+
45
87
  end
46
88
  end
@@ -12,15 +12,19 @@ module ActiveRecord
12
12
  attribute_names.each do |name|
13
13
  attrs[arel_table[name]] = typecasted_attribute_value(name)
14
14
  end
15
-
15
+
16
16
  if self.class.connection.is_a?(ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter)
17
17
  self.class.reflect_on_all_associations.each do |reflection|
18
18
  if reflection.belongs_to?
19
- add_attributes_for_belongs_to_association(reflection, attrs)
19
+ if association(reflection.name).loaded? && association(reflection.name).target == $updating_model
20
+ attrs.delete(arel_table[reflection.foreign_key])
21
+ else
22
+ add_attributes_for_belongs_to_association(reflection, attrs)
23
+ end
20
24
  elsif reflection.has_one?
21
25
  add_attributes_for_has_one_association(reflection, attrs)
22
26
  elsif reflection.collection?
23
- add_attributes_for_collection_association(reflection, attrs)
27
+ add_attributes_for_collection_association(reflection, attrs, arel_table)
24
28
  end
25
29
  end
26
30
  end
@@ -29,7 +33,7 @@ module ActiveRecord
29
33
  end
30
34
 
31
35
  def add_attributes_for_belongs_to_association(reflection, attrs)
32
- key = :"add_attributes_for_belongs_to_association#{reflection.name}"
36
+ key = :"add_attributes_for_belongs_to_association_#{reflection.name}"
33
37
  @_already_called ||= {}
34
38
  return if @_already_called[key]
35
39
  @_already_called[key]=true
@@ -47,11 +51,11 @@ module ActiveRecord
47
51
  if record.new_record? || (autosave && record.changed_for_autosave?)
48
52
  if record.new_record?
49
53
  record.send(:arel_attributes_with_values_for_create, record.attribute_names).each do |k, v|
50
- attrs[Arel::Attributes::Relation.new(k, reflection.name)] = v
54
+ attrs[Arel::Attributes::Relation.new(k, reflection.name, false, true)] = v
51
55
  end
52
56
  else
53
57
  record.send(:arel_attributes_with_values_for_update, record.attribute_names).each do |k, v|
54
- attrs[Arel::Attributes::Relation.new(k, reflection.name)] = v
58
+ attrs[Arel::Attributes::Relation.new(k, reflection.name, false, true)] = v
55
59
  end
56
60
  end
57
61
  end
@@ -84,11 +88,11 @@ module ActiveRecord
84
88
 
85
89
  if record.new_record?
86
90
  record.send(:arel_attributes_with_values_for_create, record.attribute_names).each do |k, v|
87
- attrs[Arel::Attributes::Relation.new(k, reflection.name)] = v
91
+ attrs[Arel::Attributes::Relation.new(k, reflection.name, false, true)] = v
88
92
  end
89
93
  else
90
94
  record.send(:arel_attributes_with_values_for_update, record.attribute_names).each do |k, v|
91
- attrs[Arel::Attributes::Relation.new(k, reflection.name)] = v
95
+ attrs[Arel::Attributes::Relation.new(k, reflection.name, false, true)] = v
92
96
  end
93
97
  end
94
98
  end
@@ -96,7 +100,7 @@ module ActiveRecord
96
100
  end
97
101
  end
98
102
 
99
- def add_attributes_for_collection_association(reflection, attrs)
103
+ def add_attributes_for_collection_association(reflection, attrs, arel_table=nil)
100
104
  key = :"add_attributes_for_collection_association#{reflection.name}"
101
105
  @_already_called ||= {}
102
106
  return if @_already_called[key]
@@ -108,22 +112,22 @@ module ActiveRecord
108
112
 
109
113
  if association = association_instance_get(reflection.name)
110
114
  autosave = reflection.options[:autosave]
111
- if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave)
112
115
 
113
- records.each_with_index do |record, idx|
114
- next if record.destroyed?
116
+ attrs[Arel::Attributes::EmptyRelation.new(arel_table, reflection.name, true, true)] = [] if association.target.empty?
117
+
118
+ association.target.each_with_index do |record, idx|
119
+ next if record.destroyed?
115
120
 
116
- if record.new_record?
117
- record.send(:arel_attributes_with_values_for_create, record.attribute_names).each do |k, v|
118
- attrs[Arel::Attributes::Relation.new(k, reflection.name, idx)] = v
119
- end
120
- else
121
- record.send(:arel_attributes_with_values_for_update, record.attribute_names).each do |k, v|
122
- attrs[Arel::Attributes::Relation.new(k, reflection.name, idx)] = v
123
- end
121
+ if record.new_record?
122
+ record.send(:arel_attributes_with_values_for_create, record.attribute_names).each do |k, v|
123
+ attrs[Arel::Attributes::Relation.new(k, reflection.name, idx, true)] = v
124
+ end
125
+ else
126
+ record.send(:arel_attributes_with_values_for_update, record.attribute_names).each do |k, v|
127
+ attrs[Arel::Attributes::Relation.new(k, reflection.name, idx, true)] = v
124
128
  end
125
-
126
129
  end
130
+
127
131
  end
128
132
 
129
133
  # reconstruct the scope now that we know the owner's id
@@ -0,0 +1,17 @@
1
+ module ActiveRecord
2
+ module Callbacks
3
+ private
4
+
5
+ def create_or_update(*) #:nodoc:
6
+ if self.class.connection.is_a?(ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter)
7
+ @_already_called ||= {}
8
+ self.class.reflect_on_all_associations.each do |r|
9
+ @_already_called[:"autosave_associated_records_for_#{r.name}"] = true
10
+ end
11
+ end
12
+
13
+ _run_save_callbacks { super }
14
+ end
15
+
16
+ end
17
+ end
@@ -1,3 +1,41 @@
1
+ module Arel
2
+ module Visitors
3
+ class ToSql < Arel::Visitors::Reduce
4
+
5
+ def visit_Arel_Attributes_Relation o, collector
6
+ visit(o.relation, collector)
7
+ end
8
+
9
+ end
10
+ end
11
+ end
12
+
13
+ module ActiveRecord
14
+ class PredicateBuilder # :nodoc:
15
+
16
+ def expand_from_hash(attributes)
17
+ return ["1=0"] if attributes.empty?
18
+
19
+ attributes.flat_map do |key, value|
20
+ if value.is_a?(Hash)
21
+ ka = associated_predicate_builder(key).expand_from_hash(value)
22
+ if self.table.instance_variable_get(:@klass).connection.is_a?(ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter)
23
+ ka.each { |k|
24
+ if k.left.is_a?(Arel::Attributes::Attribute) || k.left.is_a?(Arel::Attributes::Relation)
25
+ k.left = Arel::Attributes::Relation.new(k.left, key)
26
+ end
27
+ }
28
+ end
29
+ ka
30
+ else
31
+ expand(key, value)
32
+ end
33
+ end
34
+ end
35
+
36
+ end
37
+ end
38
+
1
39
  module ActiveRecord
2
40
  module FinderMethods
3
41
 
@@ -20,7 +58,11 @@ module ActiveRecord
20
58
  []
21
59
  else
22
60
  arel = relation.arel
23
- rows = connection.select_all(arel, 'SQL', arel.bind_values + relation.bound_attributes)
61
+ rows = if connection.is_a?(ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter)
62
+ connection.select_all(arel, 'SQL', arel.bind_values + relation.bound_attributes)
63
+ else
64
+ connection.select_all(arel, 'SQL', relation.bound_attributes)
65
+ end
24
66
  if join_dependency
25
67
  join_dependency.instantiate(rows, aliases)
26
68
  else
@@ -37,7 +79,7 @@ module ActiveRecord
37
79
  }
38
80
  }
39
81
 
40
- model_cache = Hash.new { |h,klass| h[klass] = {} }
82
+ model_cache = Hash.new { |h,kklass| h[kklass] = {} }
41
83
  parents = model_cache[self.base_class]
42
84
 
43
85
  result_set.each { |row_hash|
@@ -4,10 +4,36 @@ module ActiveRecord
4
4
  private
5
5
 
6
6
  def create_or_update(*args)
7
+ @updating = new_record? ? :creating : :updating
8
+ $updating_model = self
9
+
7
10
  raise ReadOnlyRecord, "#{self.class} is marked as readonly" if readonly?
8
11
  result = new_record? ? _create_record : _update_record(*args)
12
+
13
+ if self.class.connection.is_a?(ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter) && result != 0
14
+ row_hash = result.rows.first
15
+
16
+ seen = Hash.new { |h, parent_klass|
17
+ h[parent_klass] = Hash.new { |i, parent_id|
18
+ i[parent_id] = Hash.new { |j, child_klass| j[child_klass] = {} }
19
+ }
20
+ }
21
+
22
+ model_cache = Hash.new { |h,klass| h[klass] = {} }
23
+ parents = model_cache[self.class.base_class]
24
+
25
+ self.assign_attributes(row_hash.select{|k,v| self.class.column_names.include?(k.to_s) })
26
+ row_hash.select{|k,v| !self.class.column_names.include?(k.to_s) }.each do |relation_name, value|
27
+ assc = association(relation_name.to_sym)
28
+ assc.reset if assc.reflection.collection?
29
+ end
30
+
31
+ construct(self, row_hash.select{|k,v| !self.class.column_names.include?(k.to_s) }, seen, model_cache)
32
+ end
33
+
9
34
  result != false
10
- rescue Sunstone::Exception::BadRequest => e
35
+ # TODO: perhaps this can go further down the stack?
36
+ rescue Sunstone::Exception::BadRequest, Sunstone::Exception::Forbidden => e
11
37
  JSON.parse(e.message)['errors'].each do |field, message|
12
38
  if message.is_a?(Array)
13
39
  message.each { |m| errors.add(field, m) }
@@ -16,6 +42,106 @@ module ActiveRecord
16
42
  end
17
43
  end
18
44
  raise ActiveRecord::RecordInvalid
45
+ ensure
46
+ @updating = false
47
+ $updating_model = nil
48
+ end
49
+
50
+ # Creates a record with values matching those of the instance attributes
51
+ # and returns its id.
52
+ def _create_record(attribute_names = self.attribute_names)
53
+ attributes_values = arel_attributes_with_values_for_create(attribute_names)
54
+
55
+ new_id = self.class.unscoped.insert attributes_values
56
+
57
+ @new_record = false
58
+
59
+ if self.class.connection.is_a?(ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter)
60
+ new_id
61
+ else
62
+ self.id ||= new_id if self.class.primary_key
63
+ id
64
+ end
65
+ end
66
+
67
+ #!!!! TODO: I am duplicated from finder_methods.....
68
+ def construct(parent, relations, seen, model_cache)
69
+ relations.each do |key, attributes|
70
+ reflection = parent.class.reflect_on_association(key)
71
+ next unless reflection
72
+
73
+ if reflection.collection?
74
+ other = parent.association(reflection.name)
75
+ other.loaded!
76
+ else
77
+ if parent.association_cached?(reflection.name)
78
+ model = parent.association(reflection.name).target
79
+ construct(model, attributes.select{|k,v| !reflection.klass.column_names.include?(k.to_s) }, seen, model_cache)
80
+ end
81
+ end
82
+
83
+ if !reflection.collection?
84
+ construct_association(parent, reflection, attributes, seen, model_cache)
85
+ else
86
+ attributes.each do |row|
87
+ construct_association(parent, reflection, row, seen, model_cache)
88
+ end
89
+ end
90
+
91
+ end
92
+ end
93
+
94
+ #!!!! TODO: I am duplicated from finder_methods.....
95
+ def construct_association(parent, reflection, attributes, seen, model_cache)
96
+ return if attributes.nil?
97
+
98
+ klass = if reflection.polymorphic?
99
+ parent.send(reflection.foreign_type).constantize.base_class
100
+ else
101
+ reflection.klass
102
+ end
103
+ id = attributes[klass.primary_key]
104
+ model = seen[parent.class.base_class][parent.id][klass][id]
105
+
106
+ if model
107
+ construct(model, attributes.select{|k,v| !klass.column_names.include?(k.to_s) }, seen, model_cache)
108
+
109
+ other = parent.association(reflection.name)
110
+
111
+ if reflection.collection?
112
+ other.target.push(model)
113
+ else
114
+ other.target = model
115
+ end
116
+
117
+ other.set_inverse_instance(model)
118
+ else
119
+ model = construct_model(parent, reflection, id, attributes.select{|k,v| klass.column_names.include?(k.to_s) }, seen, model_cache)
120
+ seen[parent.class.base_class][parent.id][model.class.base_class][id] = model
121
+ construct(model, attributes.select{|k,v| !klass.column_names.include?(k.to_s) }, seen, model_cache)
122
+ end
123
+ end
124
+
125
+ #!!!! TODO: I am duplicated from finder_methods.....
126
+ def construct_model(record, reflection, id, attributes, seen, model_cache)
127
+ klass = if reflection.polymorphic?
128
+ record.send(reflection.foreign_type).constantize
129
+ else
130
+ reflection.klass
131
+ end
132
+
133
+ model = model_cache[klass][id] ||= klass.instantiate(attributes)
134
+ other = record.association(reflection.name)
135
+
136
+ if reflection.collection?
137
+ other.target.push(model)
138
+ else
139
+ other.target = model
140
+ end
141
+
142
+ other.set_inverse_instance(model)
143
+ model
19
144
  end
145
+
20
146
  end
21
147
  end