vex 0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (114) hide show
  1. data/Manifest +112 -0
  2. data/Rakefile +53 -0
  3. data/VERSION +1 -0
  4. data/config/README +2 -0
  5. data/config/dependencies.rb +10 -0
  6. data/config/gem.yml +7 -0
  7. data/init.rb +36 -0
  8. data/lib/nokogiri/nokogiri_ext.rb +96 -0
  9. data/lib/vex.rb +5 -0
  10. data/lib/vex/action_controller.rb +4 -0
  11. data/lib/vex/action_controller/verify_action.rb +97 -0
  12. data/lib/vex/action_controller/whitelisted_actions.rb +45 -0
  13. data/lib/vex/active_record.rb +3 -0
  14. data/lib/vex/active_record/__init__.rb +0 -0
  15. data/lib/vex/active_record/advisory_lock.rb +11 -0
  16. data/lib/vex/active_record/advisory_lock/mysql_adapter.rb +16 -0
  17. data/lib/vex/active_record/advisory_lock/sqlite_adapter.rb +78 -0
  18. data/lib/vex/active_record/belongs_to_many.rb +143 -0
  19. data/lib/vex/active_record/find_by_extension.rb +70 -0
  20. data/lib/vex/active_record/gem.rb +8 -0
  21. data/lib/vex/active_record/lite_table.rb +139 -0
  22. data/lib/vex/active_record/lite_view.rb +140 -0
  23. data/lib/vex/active_record/mass_load.rb +65 -0
  24. data/lib/vex/active_record/mysql_backup.rb +21 -0
  25. data/lib/vex/active_record/plugins/default_value_for/LICENSE.TXT +19 -0
  26. data/lib/vex/active_record/plugins/default_value_for/README.rdoc +421 -0
  27. data/lib/vex/active_record/plugins/default_value_for/Rakefile +6 -0
  28. data/lib/vex/active_record/plugins/default_value_for/init.rb +91 -0
  29. data/lib/vex/active_record/plugins/default_value_for/test.rb +279 -0
  30. data/lib/vex/active_record/plugins/default_value_for/test.sqlite3 +0 -0
  31. data/lib/vex/active_record/random_id.rb +56 -0
  32. data/lib/vex/active_record/resolver.rb +49 -0
  33. data/lib/vex/active_record/serialize_hash.rb +125 -0
  34. data/lib/vex/active_record/to_html.rb +53 -0
  35. data/lib/vex/active_record/validate.rb +76 -0
  36. data/lib/vex/active_record/validation_error_ext.rb +68 -0
  37. data/lib/vex/base.rb +2 -0
  38. data/lib/vex/base/app.rb +75 -0
  39. data/lib/vex/base/array/at_random.rb +17 -0
  40. data/lib/vex/base/array/cross.rb +26 -0
  41. data/lib/vex/base/array/each_batch.rb +32 -0
  42. data/lib/vex/base/array/parallel_map.rb +98 -0
  43. data/lib/vex/base/deprecation.rb +41 -0
  44. data/lib/vex/base/enumerable/deep.rb +95 -0
  45. data/lib/vex/base/enumerable/enumerable_ext.rb +59 -0
  46. data/lib/vex/base/enumerable/progress.rb +71 -0
  47. data/lib/vex/base/filesystem/fast_copy.rb +61 -0
  48. data/lib/vex/base/filesystem/grep.rb +34 -0
  49. data/lib/vex/base/filesystem/lock.rb +43 -0
  50. data/lib/vex/base/filesystem/lock.rb.test.lck +0 -0
  51. data/lib/vex/base/filesystem/lock.rb.test.pid +1 -0
  52. data/lib/vex/base/filesystem/make_dirs.rb +94 -0
  53. data/lib/vex/base/filesystem/parse_filename.rb +36 -0
  54. data/lib/vex/base/filesystem/tmp_file.rb +87 -0
  55. data/lib/vex/base/filesystem/write.rb +43 -0
  56. data/lib/vex/base/hash/compact.rb +38 -0
  57. data/lib/vex/base/hash/cross.rb +117 -0
  58. data/lib/vex/base/hash/easy_access.rb +141 -0
  59. data/lib/vex/base/hash/ensure_keys.rb +18 -0
  60. data/lib/vex/base/hash/extract.rb +71 -0
  61. data/lib/vex/base/hash/extras.rb +62 -0
  62. data/lib/vex/base/hash/inspect.rb +17 -0
  63. data/lib/vex/base/hash/simple_access_methods.rb +74 -0
  64. data/lib/vex/base/invalid_argument/invalid_argument.rb +97 -0
  65. data/lib/vex/base/local_conf.rb +35 -0
  66. data/lib/vex/base/net/http_ext.rb +227 -0
  67. data/lib/vex/base/net/socket_ext.rb +43 -0
  68. data/lib/vex/base/object/insp.rb +123 -0
  69. data/lib/vex/base/object/multiple_attributes.rb +58 -0
  70. data/lib/vex/base/object/singleton_methods.rb +23 -0
  71. data/lib/vex/base/object/with_benchmark.rb +110 -0
  72. data/lib/vex/base/range_array.rb +40 -0
  73. data/lib/vex/base/range_ext.rb +28 -0
  74. data/lib/vex/base/safe_token.rb +156 -0
  75. data/lib/vex/base/string/string_ext.rb +136 -0
  76. data/lib/vex/base/thread/deferred.rb +52 -0
  77. data/lib/vex/base/thread/sleep.rb +11 -0
  78. data/lib/vex/base/time/date_ext.rb +12 -0
  79. data/lib/vex/boot.rb +40 -0
  80. data/lib/vex/boot/array.rb +22 -0
  81. data/lib/vex/boot/blank.rb +41 -0
  82. data/lib/vex/boot/string.rb +60 -0
  83. data/migration/create_request_log.rb +28 -0
  84. data/r.rb +35 -0
  85. data/script/console +19 -0
  86. data/script/rebuild +7 -0
  87. data/tasks/echoe.rake +52 -0
  88. data/tasks/validate_db.rake +14 -0
  89. data/test/ar.rb +30 -0
  90. data/test/auto.rb +31 -0
  91. data/test/base-tests/local_conf.rb +25 -0
  92. data/test/base.rb +2 -0
  93. data/test/boot.rb +3 -0
  94. data/test/config/local.defaults.yml +4 -0
  95. data/test/config/local.yml +8 -0
  96. data/test/test.sqlite3 +0 -0
  97. data/test/test.sqlite3.Class#create.lck +0 -0
  98. data/test/test.sqlite3.Class#create.lck.lck +0 -0
  99. data/test/test.sqlite3.Class#create.lck.pid +1 -0
  100. data/test/test.sqlite3.Class#create.pid +1 -0
  101. data/test/test.sqlite3.LiteView.make.holders__view_dummy.lck +0 -0
  102. data/test/test.sqlite3.LiteView.make.holders__view_dummy.lck.lck +0 -0
  103. data/test/test.sqlite3.LiteView.make.holders__view_dummy.lck.pid +1 -0
  104. data/test/test.sqlite3.LiteView.make.holders__view_dummy.pid +1 -0
  105. data/test/test.sqlite3.vex.lck +0 -0
  106. data/test/test_helper.rb +49 -0
  107. data/test/tmp/copy.dat +1 -0
  108. data/test/tmp/lock.sqlite3 +0 -0
  109. data/test/tmp/lock.sqlite3.etest.lck +0 -0
  110. data/test/tmp/lock.sqlite3.etest.pid +1 -0
  111. data/test/tmp/somedata.dat +61 -0
  112. data/vex.gemspec +49 -0
  113. data/vex.tmproj +186 -0
  114. metadata +305 -0
@@ -0,0 +1,6 @@
1
+ task :default => :test
2
+
3
+ desc "Run unit tests."
4
+ task :test do
5
+ ruby "test.rb"
6
+ end
@@ -0,0 +1,91 @@
1
+ # Copyright (c) 2008 Phusion
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ module DefaultValueForPlugin
22
+ class NormalValueContainer
23
+ def initialize(value)
24
+ @value = value
25
+ end
26
+
27
+ def evaluate(instance)
28
+ return @value
29
+ end
30
+ end
31
+
32
+ class BlockValueContainer
33
+ def initialize(block)
34
+ @block = block
35
+ end
36
+
37
+ def evaluate(instance)
38
+ return @block.call(instance)
39
+ end
40
+ end
41
+
42
+ module ClassMethods
43
+ def default_value_for(attribute, value = nil, &block)
44
+ if !method_defined?(:initialize_with_defaults)
45
+ include(InstanceMethods)
46
+ alias_method_chain :initialize, :defaults
47
+ class_inheritable_accessor :_default_attribute_values
48
+ self._default_attribute_values = ActiveSupport::OrderedHash.new
49
+ end
50
+ if block_given?
51
+ container = BlockValueContainer.new(block)
52
+ else
53
+ container = NormalValueContainer.new(value)
54
+ end
55
+ _default_attribute_values[attribute.to_s] = container
56
+ end
57
+
58
+ def default_values(values)
59
+ values.each_pair do |key, value|
60
+ if value.kind_of? Proc
61
+ default_value_for(key, &value)
62
+ else
63
+ default_value_for(key, value)
64
+ end
65
+ end
66
+ end
67
+ end
68
+
69
+ module InstanceMethods
70
+ def initialize_with_defaults(attrs = nil)
71
+ initialize_without_defaults(attrs) do
72
+ if attrs
73
+ stringified_attrs = attrs.stringify_keys
74
+ safe_attrs = remove_attributes_protected_from_mass_assignment(stringified_attrs)
75
+ safe_attribute_names = safe_attrs.keys.map do |x|
76
+ x.to_s
77
+ end
78
+ end
79
+ self.class._default_attribute_values.each do |attribute, container|
80
+ if safe_attribute_names.nil? || safe_attribute_names.none? { |attr_name| attr_name =~ /^#{attribute}($|\()/ }
81
+ __send__("#{attribute}=", container.evaluate(self))
82
+ changed_attributes.delete(attribute)
83
+ end
84
+ end
85
+ yield(self) if block_given?
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ ActiveRecord::Base.extend(DefaultValueForPlugin::ClassMethods)
@@ -0,0 +1,279 @@
1
+ # Copyright (c) 2008, 2009 Phusion
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'rubygems'
22
+ require 'active_record'
23
+ require 'test/unit'
24
+ require File.dirname(__FILE__) + '/init'
25
+ Dir.chdir(File.dirname(__FILE__))
26
+
27
+ if RUBY_PLATFORM == "java"
28
+ database_adapter = "jdbcsqlite3"
29
+ else
30
+ database_adapter = "sqlite3"
31
+ end
32
+
33
+ File.unlink('test.sqlite3') rescue nil
34
+ ActiveRecord::Base.logger = Logger.new(STDERR)
35
+ ActiveRecord::Base.logger.level = Logger::WARN
36
+ ActiveRecord::Base.establish_connection(
37
+ :adapter => database_adapter,
38
+ :database => 'test.sqlite3'
39
+ )
40
+ ActiveRecord::Base.connection.create_table(:users, :force => true) do |t|
41
+ t.string :username
42
+ t.integer :default_number
43
+ end
44
+ ActiveRecord::Base.connection.create_table(:numbers, :force => true) do |t|
45
+ t.string :type
46
+ t.integer :number
47
+ t.integer :count, :null => false, :default => 1
48
+ t.integer :user_id
49
+ t.timestamp :timestamp
50
+ end
51
+
52
+ class User < ActiveRecord::Base
53
+ has_many :numbers, :class_name => 'TestClass'
54
+ end
55
+
56
+ class Number < ActiveRecord::Base
57
+ end
58
+
59
+ class DefaultValuePluginTest < Test::Unit::TestCase
60
+ def setup
61
+ Number.create(:number => 9876)
62
+ end
63
+
64
+ def teardown
65
+ Number.delete_all
66
+ end
67
+
68
+ def define_model_class(name = "TestClass", parent_class_name = "ActiveRecord::Base", &block)
69
+ Object.send(:remove_const, name) rescue nil
70
+ eval("class #{name} < #{parent_class_name}; end", TOPLEVEL_BINDING)
71
+ klass = eval(name, TOPLEVEL_BINDING)
72
+ klass.class_eval do
73
+ set_table_name 'numbers'
74
+ end
75
+ klass.class_eval(&block) if block_given?
76
+ end
77
+
78
+ def test_default_value_can_be_passed_as_argument
79
+ define_model_class do
80
+ default_value_for(:number, 1234)
81
+ end
82
+ object = TestClass.new
83
+ assert_equal 1234, object.number
84
+ end
85
+
86
+ def test_default_value_can_be_passed_as_block
87
+ define_model_class do
88
+ default_value_for(:number) { 1234 }
89
+ end
90
+ object = TestClass.new
91
+ assert_equal 1234, object.number
92
+ end
93
+
94
+ def test_works_with_create
95
+ define_model_class do
96
+ default_value_for :number, 1234
97
+ end
98
+ TestClass.create
99
+ assert_not_nil TestClass.find_by_number(1234)
100
+ end
101
+
102
+ def test_overwrites_db_default
103
+ define_model_class do
104
+ default_value_for :count, 1234
105
+ end
106
+ object = TestClass.new
107
+ assert_equal 1234, object.count
108
+ end
109
+
110
+ def test_doesnt_overwrite_values_provided_by_mass_assignment
111
+ define_model_class do
112
+ default_value_for :number, 1234
113
+ end
114
+ object = TestClass.new(:number => 1, :count => 2)
115
+ assert_equal 1, object.number
116
+ end
117
+
118
+ def test_doesnt_overwrite_values_provided_by_multiparameter_assignment
119
+ define_model_class do
120
+ default_value_for :timestamp, Time.mktime(2000, 1, 1)
121
+ end
122
+ timestamp = Time.mktime(2009, 1, 1)
123
+ object = TestClass.new('timestamp(1i)' => '2009', 'timestamp(2i)' => '1', 'timestamp(3i)' => '1')
124
+ assert_equal timestamp, object.timestamp
125
+ end
126
+
127
+ def test_doesnt_overwrite_values_provided_by_constructor_block
128
+ define_model_class do
129
+ default_value_for :number, 1234
130
+ end
131
+ object = TestClass.new do |x|
132
+ x.number = 1
133
+ x.count = 2
134
+ end
135
+ assert_equal 1, object.number
136
+ end
137
+
138
+ def test_doesnt_overwrite_explicitly_provided_nil_values_in_mass_assignment
139
+ define_model_class do
140
+ default_value_for :number, 1234
141
+ end
142
+ object = TestClass.new(:number => nil)
143
+ assert_nil object.number
144
+ end
145
+
146
+ def test_default_values_are_inherited
147
+ define_model_class("TestSuperClass") do
148
+ default_value_for :number, 1234
149
+ end
150
+ define_model_class("TestClass", "TestSuperClass")
151
+ object = TestClass.new
152
+ assert_equal 1234, object.number
153
+ end
154
+
155
+ def test_doesnt_set_default_on_saved_records
156
+ define_model_class do
157
+ default_value_for :number, 1234
158
+ end
159
+ assert_equal 9876, TestClass.find(:first).number
160
+ end
161
+
162
+ def test_also_works_on_attributes_that_arent_database_columns
163
+ define_model_class do
164
+ default_value_for :hello, "hi"
165
+ attr_accessor :hello
166
+ end
167
+ object = TestClass.new
168
+ assert_equal 'hi', object.hello
169
+ end
170
+
171
+ def test_constructor_ignores_forbidden_mass_assignment_attributes
172
+ define_model_class do
173
+ default_value_for :number, 1234
174
+ attr_protected :number
175
+ end
176
+ object = TestClass.new(:number => 5678, :count => 987)
177
+ assert_equal 1234, object.number
178
+ assert_equal 987, object.count
179
+ end
180
+
181
+ def test_doesnt_conflict_with_overrided_initialize_method_in_model_class
182
+ define_model_class do
183
+ def initialize(attrs = {})
184
+ @initialized = true
185
+ super(:count => 5678)
186
+ end
187
+
188
+ default_value_for :number, 1234
189
+ end
190
+ object = TestClass.new
191
+ assert_equal 1234, object.number
192
+ assert_equal 5678, object.count
193
+ assert object.instance_variable_get('@initialized')
194
+ end
195
+
196
+ def test_model_instance_is_passed_to_the_given_block
197
+ $instance = nil
198
+ define_model_class do
199
+ default_value_for :number do |n|
200
+ $instance = n
201
+ end
202
+ end
203
+ object = TestClass.new
204
+ assert_same object, $instance
205
+ end
206
+
207
+ def test_can_specify_default_value_via_association
208
+ user = User.create(:username => 'Kanako', :default_number => 123)
209
+ define_model_class do
210
+ belongs_to :user
211
+
212
+ default_value_for :number do |n|
213
+ n.user.default_number
214
+ end
215
+ end
216
+ object = user.numbers.create
217
+ assert_equal 123, object.number
218
+ end
219
+
220
+ def test_default_values
221
+ define_model_class do
222
+ default_values :type => "normal",
223
+ :number => lambda { 10 + 5 }
224
+ end
225
+
226
+ object = TestClass.new
227
+ assert_equal("normal", object.type)
228
+ assert_equal(15, object.number)
229
+ end
230
+
231
+ def test_default_value_order
232
+ define_model_class do
233
+ default_value_for :count, 5
234
+ default_value_for :number do |this|
235
+ this.count * 2
236
+ end
237
+ end
238
+ object = TestClass.new
239
+ assert_equal(5, object.count)
240
+ assert_equal(10, object.number)
241
+ end
242
+
243
+ def test_attributes_with_default_values_are_not_marked_as_changed
244
+ define_model_class do
245
+ default_value_for :count, 5
246
+ default_value_for :number, 2
247
+ end
248
+
249
+ object = TestClass.new
250
+ assert(!object.changed?)
251
+ assert_equal([], object.changed)
252
+
253
+ object.type = "foo"
254
+ assert(object.changed?)
255
+ assert_equal(["type"], object.changed)
256
+ end
257
+
258
+ def test_default_values_are_not_duplicated
259
+ define_model_class do
260
+ set_table_name "users"
261
+ default_value_for :username, "hello"
262
+ end
263
+ user1 = TestClass.new
264
+ user1.username << " world"
265
+ user2 = TestClass.new
266
+ assert_equal("hello world", user2.username)
267
+ end
268
+
269
+ def test_constructor_does_not_affect_the_hash_passed_to_it
270
+ define_model_class do
271
+ default_value_for :count, 5
272
+ end
273
+
274
+ options = { :count => 5, :user_id => 1 }
275
+ options_dup = options.dup
276
+ object = TestClass.new(options)
277
+ assert_equal(options_dup, options)
278
+ end
279
+ end
@@ -0,0 +1,56 @@
1
+ module ActiveRecord::RandomID
2
+ module Generator
3
+ def self.string
4
+ # This puts 40 random bits into a string.
5
+ ActiveSupport::SecureRandom.random_number(0xffffffffff).to_s(36)
6
+ end
7
+
8
+ def self.integer
9
+ # This gives us 31 bits of random data.
10
+ ActiveSupport::SecureRandom.random_number 0x7fffffff
11
+ end
12
+
13
+ def self.large
14
+ # This gives us 63 bits of random data.
15
+ ActiveSupport::SecureRandom.random_number 0x7fffffffffffffff
16
+ end
17
+ end
18
+
19
+ def with_random_column(column, generator = :integer)
20
+ before_validation do |rec|
21
+ next if rec.send(column)
22
+ rec.send "#{column}=", ActiveRecord::RandomID::Generator.send(generator)
23
+ end
24
+ end
25
+
26
+ def with_random_id(generator = :integer)
27
+ with_random_column primary_key, generator
28
+ end
29
+ end
30
+
31
+ class ActiveRecord::Base
32
+ extend ActiveRecord::RandomID
33
+ end
34
+
35
+ module ActiveRecord::RandomID::Etest
36
+ class RandomBase < ActiveRecord::Base
37
+ lite_table do
38
+ text :parameters
39
+ end
40
+
41
+ with_random_id
42
+ end
43
+
44
+ def test_random_id
45
+ rb = RandomBase.create!
46
+ uids = [ rb.id ]
47
+ rb.save
48
+ uids << rb.id
49
+
50
+ rb.reload
51
+ uids << rb.id
52
+
53
+ assert_not_nil(rb.id)
54
+ assert_equal([ rb.id ], uids.uniq)
55
+ end
56
+ end
@@ -0,0 +1,49 @@
1
+ module ActiveRecord::Resolver
2
+ #
3
+ # resolves an object.
4
+ def resolve(obj)
5
+ case obj
6
+ when self then obj
7
+ when String then self.find(Integer(obj))
8
+ when Array then obj.map { |o| resolve(o) }
9
+ else self.find(obj)
10
+ end
11
+ end
12
+ end
13
+
14
+ ActiveRecord::Base.extend ActiveRecord::Resolver
15
+
16
+ module ActiveRecord::FindByExtension::Etest
17
+ class Data < ActiveRecord::Base
18
+ end
19
+
20
+ def setup
21
+ Data.lite_table do
22
+ string :name
23
+ string :age
24
+ end
25
+
26
+ Data.create! :name => "name", :age => 2
27
+ Data.create! :name => "name", :age => 3
28
+ Data.create! :name => "name", :age => 4
29
+ Data.create! :name => "name2", :age => 2
30
+ Data.create! :name => "name2", :age => 3
31
+ Data.create! :name => "name2", :age => 4
32
+
33
+ assert_equal(6, Data.count)
34
+ end
35
+
36
+ def teardown
37
+ Data.destroy_all
38
+ end
39
+
40
+ def test_resolve
41
+ data2 = Data.find_by :name => "name", :age => 2
42
+ data3 = Data.find_by :name => "name", :age => 3
43
+
44
+ assert_equal data2, Data.resolve(data2)
45
+ assert_equal data2, Data.resolve(data2.id)
46
+ assert_equal data2, Data.resolve(data2.id.to_s)
47
+ assert_equal [ data2, data3 ].sort_by(&:id), Data.resolve([data2.id, data3.id]).sort_by(&:id)
48
+ end
49
+ end