active_worker 0.50.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/.document +5 -0
  2. data/.rvmrc +1 -0
  3. data/Gemfile +22 -0
  4. data/Gemfile.lock +77 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.rdoc +19 -0
  7. data/Rakefile +46 -0
  8. data/VERSION +1 -0
  9. data/active_worker.gemspec +108 -0
  10. data/lib/active_worker.rb +29 -0
  11. data/lib/active_worker/behavior/acts_as_root_object.rb +80 -0
  12. data/lib/active_worker/behavior/can_be_notified.rb +23 -0
  13. data/lib/active_worker/behavior/create_from_error.rb +21 -0
  14. data/lib/active_worker/behavior/execute_concurrently.rb +142 -0
  15. data/lib/active_worker/behavior/has_modes.rb +44 -0
  16. data/lib/active_worker/behavior/has_root_object.rb +50 -0
  17. data/lib/active_worker/behavior/hashable.rb +79 -0
  18. data/lib/active_worker/configuration.rb +143 -0
  19. data/lib/active_worker/controller.rb +112 -0
  20. data/lib/active_worker/event.rb +68 -0
  21. data/lib/active_worker/expandable.rb +77 -0
  22. data/lib/active_worker/failure_event.rb +16 -0
  23. data/lib/active_worker/finished_event.rb +9 -0
  24. data/lib/active_worker/host_information.rb +13 -0
  25. data/lib/active_worker/job_queue/job_executer.rb +52 -0
  26. data/lib/active_worker/job_queue/queue_manager.rb +46 -0
  27. data/lib/active_worker/job_queue/run_remotely.rb +52 -0
  28. data/lib/active_worker/modes_map.rb +37 -0
  29. data/lib/active_worker/notification_event.rb +9 -0
  30. data/lib/active_worker/parent_event.rb +5 -0
  31. data/lib/active_worker/started_event.rb +10 -0
  32. data/lib/active_worker/templatable.rb +46 -0
  33. data/lib/active_worker/template.rb +41 -0
  34. data/lib/active_worker/termination_event.rb +21 -0
  35. data/test/mongoid.yml +28 -0
  36. data/test/test_acts_as_root_object.rb +123 -0
  37. data/test/test_can_be_notified.rb +44 -0
  38. data/test/test_configuration.rb +281 -0
  39. data/test/test_controller.rb +205 -0
  40. data/test/test_event.rb +75 -0
  41. data/test/test_execute_concurrently.rb +134 -0
  42. data/test/test_expandable.rb +113 -0
  43. data/test/test_failure_event.rb +69 -0
  44. data/test/test_finished_event.rb +35 -0
  45. data/test/test_has_modes.rb +56 -0
  46. data/test/test_helper.rb +120 -0
  47. data/test/test_integration.rb +56 -0
  48. data/test/test_job_executer.rb +65 -0
  49. data/test/test_queue_manager.rb +106 -0
  50. data/test/test_run_remotely.rb +63 -0
  51. data/test/test_started_event.rb +23 -0
  52. data/test/test_templatable.rb +45 -0
  53. data/test/test_template.rb +29 -0
  54. data/test/test_termination_event.rb +28 -0
  55. metadata +201 -0
@@ -0,0 +1,37 @@
1
+ module ActiveWorker
2
+ class ModesMap
3
+
4
+ attr_reader :modes_hash
5
+
6
+ def initialize
7
+ @modes_hash = Hash.new do |hash, key|
8
+ inner_hash = {}
9
+ hash[key] = inner_hash
10
+ inner_hash
11
+ end
12
+ end
13
+
14
+ def add_mode(mode_name, field_maps)
15
+ modes_hash[normalize_mode_name(mode_name)].merge! field_maps
16
+ end
17
+
18
+ def normalize_mode_name(mode_name)
19
+ mode_name.to_sym
20
+ end
21
+
22
+ def modes
23
+ modes_hash.keys
24
+ end
25
+
26
+ def supports?(mode_name)
27
+ modes.include? normalize_mode_name(mode_name)
28
+ end
29
+
30
+ def mode(mode_name)
31
+ if supports? mode_name
32
+ modes_hash[normalize_mode_name(mode_name)]
33
+ end
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,9 @@
1
+ module ActiveWorker
2
+ class NotificationEvent < ActiveWorker::Event
3
+
4
+ def generate_message
5
+ "#{configuration.event_name} has been notified"
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ module ActiveWorker
2
+ class ParentEvent < ActiveWorker::Event
3
+ extend Behavior::CreateFromError
4
+ end
5
+ end
@@ -0,0 +1,10 @@
1
+ module ActiveWorker
2
+ class StartedEvent < ActiveWorker::Event
3
+ def generate_message
4
+ "#{configuration.event_name} started"
5
+ end
6
+
7
+ after_create :notify_root_of_child_started
8
+
9
+ end
10
+ end
@@ -0,0 +1,46 @@
1
+ module ActiveWorker
2
+ module Templatable
3
+
4
+ module ClassMethods
5
+ def templates_with_names
6
+ Template.with_names(self)
7
+ end
8
+ end
9
+
10
+ def self.included(base)
11
+ base.field :renderable, :type => Boolean, :default => true
12
+ base.field :template_name
13
+
14
+ base.extend(ClassMethods)
15
+ end
16
+
17
+ def find_template
18
+ child_template_ids = configurations.map(&:find_template).map(&:id)
19
+
20
+ attributes_for_template = {}
21
+ attributes_for_template[:configuration_type] = self.class.name
22
+
23
+ if child_template_ids.any?
24
+ attributes_for_template[:child_template_ids] = child_template_ids
25
+ end
26
+
27
+ self.class.template_fields.each do |field|
28
+ attributes_for_template[field] = read_attribute(field)
29
+ end
30
+
31
+ template = Template.find_or_create_by(attributes_for_template)
32
+ template.name = template_name if template_name && (! template_name.empty?)
33
+ template.save!
34
+ template
35
+ end
36
+
37
+ def template_name_or(input_string)
38
+ if template_name && (! template_name.empty?)
39
+ template_name
40
+ else
41
+ input_string
42
+ end
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,41 @@
1
+ module ActiveWorker
2
+ class Template
3
+ include Mongoid::Document
4
+
5
+ has_and_belongs_to_many :child_templates, :class_name => "ActiveWorker::Template", :inverse_of => :parent_templates
6
+
7
+ field :name
8
+ field :configuration_type
9
+
10
+ scope :with_names, ->(configuration_class) { where(:name.exists => true, :configuration_type => configuration_class.name)}
11
+
12
+ def name_for_display
13
+ if(name && !(name.empty?))
14
+ name
15
+ else
16
+ configuration_type.split("::")[0..-2].join(" ")
17
+ end
18
+ end
19
+
20
+ def build_configuration
21
+ configuration = configuration_class.new
22
+
23
+ configuration_class.template_fields.each do |field|
24
+ configuration.write_attribute(field,read_attribute(field))
25
+ end
26
+
27
+ configuration.template_name = name
28
+
29
+ child_template_ids.each do |child_id|
30
+ child = Template.find(child_id)
31
+ configuration.configurations << child.build_configuration
32
+ end
33
+
34
+ configuration
35
+ end
36
+
37
+ def configuration_class
38
+ configuration_type.constantize
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,21 @@
1
+ module ActiveWorker
2
+ class TerminationEvent < ActiveWorker::FinishedEvent
3
+
4
+ def self.from_termination(root_configuration)
5
+ events = []
6
+ root_configuration.configurations_for_events.each do |configuration|
7
+ events << create_termination_from_configuration(configuration) unless configuration.completed?
8
+ end
9
+ events
10
+ end
11
+
12
+ def self.create_termination_from_configuration(configuration)
13
+ constructor_options = {
14
+ :message => "#{configuration.event_name} was terminated",
15
+ :configuration => configuration,
16
+ }
17
+ create! constructor_options
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,28 @@
1
+ test:
2
+ sessions:
3
+ default:
4
+ database: active_worker_test
5
+ hosts:
6
+ - localhost:27017
7
+ options:
8
+ consistency: :strong
9
+ safe: true
10
+ identity_map_enabled: true
11
+ options:
12
+ preload_models: true
13
+
14
+
15
+
16
+ #settings: &settings
17
+ # autocreate_indexes: true
18
+ # identity_map_enabled: true
19
+ #
20
+ #defaults: &defaults
21
+ # <<: *settings
22
+ # host: localhost
23
+ # port: 27017
24
+ #
25
+ #test:
26
+ # <<: *defaults
27
+ # database: active_worker_test
28
+ # logger: false
@@ -0,0 +1,123 @@
1
+ require_relative "test_helper"
2
+
3
+ module ActiveWorker
4
+ class ActsAsRootObjectTest < ActiveSupport::TestCase
5
+
6
+
7
+ test "renderable configuration hashes returns array of configuration hashes" do
8
+ root = Rootable.create
9
+ topconfig1 = root.configurations.create({}, TemplatableTopConfig)
10
+ topconfig2 = root.configurations.create({}, TemplatableTopConfig)
11
+ child_config1 = topconfig2.configurations.create({}, TemplatableChildConfig)
12
+
13
+ assert_equal 2, root.renderable_configuration_hashes.size
14
+ assert_equal 1, root.renderable_configuration_hashes[1]["configurations"].size
15
+
16
+ end
17
+
18
+ test "renderable configurations hashes only returns with renderable configurations" do
19
+
20
+ root = Rootable.create
21
+
22
+ topconfig1 = root.configurations.create({}, TemplatableTopConfig)
23
+ topconfig2 = root.configurations.create({}, TopConfig)
24
+ templatable_child_config1 = topconfig1.configurations.create({}, TemplatableChildConfig)
25
+ child_config1 = topconfig1.configurations.create({}, ChildConfig)
26
+
27
+ templatable_child_config2 = topconfig2.configurations.create({}, TemplatableChildConfig)
28
+ child_config2 = topconfig2.configurations.create({}, ChildConfig)
29
+
30
+ renderable_hashes = root.renderable_configuration_hashes
31
+
32
+ assert_equal 1, renderable_hashes.size
33
+ assert_equal 1, renderable_hashes[0]["configurations"].size
34
+
35
+ end
36
+
37
+ test "sets flags on immediate child configurations" do
38
+ root = Rootable.create flags: {"flag" => true}
39
+
40
+ topconfig1 = root.configurations.create({}, TemplatableTopConfig)
41
+ topconfig2 = root.configurations.create({}, TopConfig)
42
+ root.set_flags
43
+ topconfig1.reload
44
+ topconfig2.reload
45
+
46
+ templatable_child_config1 = topconfig1.configurations.create({}, TemplatableChildConfig)
47
+ child_config1 = topconfig1.configurations.create({}, ChildConfig)
48
+
49
+ templatable_child_config2 = topconfig2.configurations.create({}, TemplatableChildConfig)
50
+ child_config2 = topconfig2.configurations.create({}, ChildConfig)
51
+
52
+ assert topconfig1.flags["flag"]
53
+ assert topconfig2.flags["flag"]
54
+ assert templatable_child_config1.flags["flag"]
55
+ assert child_config1.flags["flag"]
56
+ assert templatable_child_config2.flags["flag"]
57
+ assert child_config2.flags["flag"]
58
+ end
59
+
60
+ test "is completed when configurations complete" do
61
+ root = Rootable.create
62
+ topconfig1 = root.configurations.create({}, TemplatableTopConfig)
63
+ topconfig2 = root.configurations.create({}, TemplatableTopConfig)
64
+
65
+ root.reload
66
+ assert_equal false, root.completed?
67
+
68
+ topconfig1.finished
69
+
70
+ root.reload
71
+ assert_equal false, root.completed?
72
+
73
+ topconfig2.finished
74
+
75
+ root.reload
76
+ assert_equal true, root.completed?
77
+
78
+ end
79
+
80
+ test "duration calculates when completed" do
81
+ root = Rootable.create
82
+ topconfig1 = root.configurations.create({}, TemplatableTopConfig)
83
+
84
+ initial_duration = root.duration
85
+
86
+ assert initial_duration > 0
87
+
88
+ topconfig1.finished
89
+
90
+ final_duration = root.duration
91
+
92
+ assert initial_duration < final_duration
93
+ end
94
+
95
+ test "duration returns 0 if completed but missing finished_at" do
96
+ root = Rootable.create
97
+
98
+ root.root_object_finished = true
99
+
100
+ duration = root.duration
101
+
102
+ assert_equal 0, duration
103
+ end
104
+
105
+
106
+ test "sets finished_at correctly" do
107
+ root = Rootable.create
108
+ topconfig1 = TemplatableTopConfig.create
109
+ root.configurations << topconfig1
110
+
111
+ root.reload
112
+ assert_nil root.finished_at
113
+
114
+ topconfig1.finished
115
+ root.reload
116
+
117
+ assert root.completed?
118
+ assert root.finished_at.to_i > 0
119
+ end
120
+
121
+ end
122
+
123
+ end
@@ -0,0 +1,44 @@
1
+ require_relative "test_helper"
2
+
3
+ module ActiveWorker
4
+
5
+ class CanBeNotifiedTest < ActiveSupport::TestCase
6
+
7
+ class Configuration < ActiveWorker::Configuration
8
+ include Expandable
9
+ config_field :wait_for_notifications, default: true
10
+ config_field :nodes
11
+
12
+ def expansion_maps_for_threads
13
+ nodes.split(",").map { |node| {nodes: node} }
14
+ end
15
+ end
16
+
17
+ class Controller < ActiveWorker::Controller
18
+ include Behavior::CanBeNotified
19
+
20
+ def execute
21
+ sleep 0.1 until configuration.notified?
22
+ end
23
+ end
24
+
25
+ test "controller forwards notifications" do
26
+ Controller::ClassMethods::SLEEP_DURATION = 0.1
27
+
28
+ nodes = "thread1, thread2, thread3"
29
+ configuration = CanBeNotifiedTest::Configuration.create nodes: nodes
30
+
31
+ configuration.launch
32
+ sleep 0.1 until (StartedEvent.count == nodes.split(",").size)
33
+ configuration.notify
34
+
35
+ wait_for_all_configurations
36
+ assert_no_failures
37
+
38
+ assert_equal 3, ActiveWorker::FinishedEvent.count
39
+ end
40
+
41
+
42
+ end
43
+
44
+ end
@@ -0,0 +1,281 @@
1
+ require_relative "test_helper"
2
+
3
+
4
+ module ActiveWorker
5
+ class ConfigurationTest < ActiveSupport::TestCase
6
+
7
+ test "can scope a child configuration type using mine" do
8
+ parent_config = TopConfig.create(top_field: "top field")
9
+ child_config0 = ChildConfig.create(child_field: "child field", parent_configuration: parent_config)
10
+ child_config1 = ChildConfig.create(child_field: "different child field")
11
+
12
+ assert_equal 1, parent_config.child_configs.count
13
+ assert_equal child_config0, parent_config.child_configs.first
14
+
15
+ end
16
+
17
+ test "Child Config is of the correct type" do
18
+ parent_config = TopConfig.create(top_field: "top field")
19
+ child_config0 = ChildConfig.create(child_field: "child field", parent_configuration: parent_config)
20
+
21
+ assert_equal TopConfig, parent_config.class
22
+
23
+ child_config = ChildConfig.where(parent_configuration_id: parent_config.to_param).first
24
+ assert_equal ChildConfig, child_config.class
25
+
26
+ end
27
+
28
+ test "completed?" do
29
+ config = TopConfig.create
30
+ assert_equal false, config.completed?
31
+ FinishedEvent.create configuration: config
32
+ assert config.completed?
33
+ end
34
+
35
+ test "passses down root object when saved" do
36
+ root = Rootable.create
37
+ config = TopConfig.new
38
+ root.configurations << config
39
+ config2 = ChildConfig.new parent_configuration: config
40
+
41
+ config.save!
42
+ config2.save!
43
+ assert_equal root, config2.root_object
44
+ end
45
+
46
+ test "passes down flags when saved" do
47
+ root = Rootable.create flags: {"analyze_performance" => true}
48
+ config = TopConfig.new flags: root.flags
49
+ root.configurations << config
50
+ config2 = ChildConfig.new parent_configuration: config
51
+
52
+ config.save!
53
+ config2.save!
54
+ assert config2.flags["analyze_performance"]
55
+ end
56
+
57
+ test "can get configuration hierarchy" do
58
+ config = TopConfig.create
59
+ config2 = ChildConfig.create parent_configuration: config
60
+ config3 = ChildConfig.create parent_configuration: config
61
+ config4 = ChildConfig.create parent_configuration: config3
62
+ config5 = ChildConfig.create parent_configuration: config3
63
+
64
+ root = Rootable.create
65
+ root.configurations << config
66
+
67
+ top_config = Configuration.get_as_hash_by_root_object(root).first
68
+
69
+ assert_equal config.to_param, top_config["_id"]
70
+ assert_equal config2.to_param, top_config["configurations"][0]["_id"]
71
+ assert_equal config3.to_param, top_config["configurations"][1]["_id"]
72
+
73
+ assert_equal config4.to_param, top_config["configurations"][1]["configurations"][0]["_id"]
74
+ assert_equal config5.to_param, top_config["configurations"][1]["configurations"][1]["_id"]
75
+ end
76
+
77
+ test "can get configurations as flat array" do
78
+ config = TopConfig.create
79
+ config2 = ChildConfig.create parent_configuration: config
80
+ config3 = ChildConfig.create parent_configuration: config
81
+ config4 = ChildConfig.create parent_configuration: config3
82
+ config5 = ChildConfig.create parent_configuration: config3
83
+
84
+ root = Rootable.create
85
+ root.configurations << config
86
+
87
+ configs = Configuration.get_as_flat_hash_by_root_object(root)
88
+
89
+ assert_equal 5, configs.size
90
+ end
91
+
92
+ test "can get renderable configuration hierarchy" do
93
+ root = Rootable.create
94
+ config = TopConfig.create renderable: true
95
+ root.configurations << config
96
+ config2 = ChildConfig.create parent_configuration: config, renderable: true
97
+ config3 = ChildConfig.create parent_configuration: config, renderable: false
98
+ config4 = ChildConfig.create parent_configuration: config3, renderable: true
99
+ config5 = ChildConfig.create parent_configuration: config3, renderable: false
100
+ config6 = ChildConfig.create parent_configuration: config2, renderable: true
101
+ config7 = ChildConfig.create parent_configuration: config6, renderable: false
102
+ config8 = ChildConfig.create parent_configuration: config6, renderable: true
103
+
104
+ top_config = Configuration.get_renderable_hash_by_root_object(root).first
105
+ assert_equal config.to_param, top_config["_id"]
106
+ assert_equal 1, top_config["configurations"].size
107
+ assert_equal config2.to_param, top_config["configurations"][0]["_id"]
108
+
109
+ assert_equal config6.to_param, top_config["configurations"][0]["configurations"][0]["_id"]
110
+ assert_equal 1, top_config["configurations"][0]["configurations"].size
111
+ assert_equal config8.to_param, top_config["configurations"][0]["configurations"][0]["configurations"][0]["_id"]
112
+ end
113
+
114
+ test "can get renderable hash for given configuration" do
115
+ config = TopConfig.create renderable: true
116
+ config2 = ChildConfig.create parent_configuration: config, renderable: true
117
+ config3 = ChildConfig.create parent_configuration: config, renderable: false
118
+
119
+ top_config = Configuration.renderable_hash_for_configuration(config.id)
120
+
121
+ assert_equal config.to_param, top_config["_id"]
122
+ assert_equal 1, top_config["configurations"].size
123
+ assert_equal config2.to_param, top_config["configurations"][0]["_id"]
124
+ assert_equal "ActiveWorker::TopConfig", top_config["_type"]
125
+ end
126
+
127
+ test "Base Configurations can be hashable with type" do
128
+ config = Configuration.create renderable: true
129
+ config2 = Configuration.create parent_configuration: config, renderable: true
130
+ config3 = Configuration.create parent_configuration: config, renderable: false
131
+
132
+ top_config = Configuration.renderable_hash_for_configuration(config.id)
133
+
134
+ assert_equal config.to_param.to_s, top_config["_id"]
135
+ assert_equal 1, top_config["configurations"].size
136
+ assert_equal config2.to_param.to_s, top_config["configurations"][0]["_id"]
137
+
138
+ assert_equal "ActiveWorkerConfiguration", top_config["_type"]
139
+ assert_equal "ActiveWorkerConfiguration", top_config["configurations"][0]["_type"]
140
+
141
+ end
142
+
143
+ test "can load hashes from configurations that no longer exist" do
144
+ module SoonToNotExist
145
+ class TopConfig < Configuration
146
+ field :top_field
147
+
148
+ def child_configs
149
+ ChildConfig.mine(self).all
150
+ end
151
+ end
152
+
153
+ class ChildConfig < Configuration
154
+ field :child_field
155
+ end
156
+ end
157
+ root = Rootable.create
158
+ config = SoonToNotExist::TopConfig.create
159
+ root.configurations << config
160
+ config2 = SoonToNotExist::ChildConfig.create parent_configuration: config
161
+ id1 = config.to_param
162
+ id2 = config2.to_param
163
+
164
+ ActiveWorker::ConfigurationTest::SoonToNotExist.send(:remove_const, :TopConfig)
165
+ ActiveWorker::ConfigurationTest::SoonToNotExist.send(:remove_const, :ChildConfig)
166
+
167
+ assert_raise NameError do
168
+ SoonToNotExist::TopConfig
169
+ end
170
+ assert_raise NameError do
171
+ SoonToNotExist::ChildConfig
172
+ end
173
+
174
+ top_config = Configuration.get_as_hash_by_root_object(root).first
175
+ assert_equal id1, top_config["_id"]
176
+ assert_equal id2, top_config["configurations"][0]["_id"]
177
+
178
+ end
179
+
180
+ test "can create started event" do
181
+ configuration = Configuration.create
182
+
183
+ configuration.started
184
+ assert_equal 1, StartedEvent.where(configuration_id: configuration.to_param).size
185
+ assert_match /#{configuration.event_name}/, StartedEvent.where(configuration_id: configuration.to_param).first.message
186
+ end
187
+
188
+ test "can create finished event" do
189
+ configuration = Configuration.create
190
+
191
+ configuration.finished
192
+ assert_equal 1, FinishedEvent.where(configuration_id: configuration.to_param).size
193
+ assert_match /#{configuration.event_name}/, FinishedEvent.where(configuration_id: configuration.to_param).first.message
194
+ end
195
+
196
+ test "can be notified" do
197
+ configuration = Configuration.create
198
+ assert_equal false, configuration.notified?
199
+ configuration.notify
200
+ assert configuration.notified?
201
+ end
202
+
203
+ test "can treat as hash" do
204
+ config = Configuration.create value: "value"
205
+
206
+ assert_equal "value", config[:value]
207
+
208
+ config[:value]= "value2"
209
+
210
+ assert_equal "value2", config[:value]
211
+
212
+ assert_nil config[:bad_method]
213
+
214
+ end
215
+
216
+ test "can retrieve hash of config fields" do
217
+ class TestConfig < Configuration
218
+ config_field :config_field
219
+ template_field :template_field
220
+ field :other_field
221
+ end
222
+
223
+ config = TestConfig.create config_field: "config_field",
224
+ template_field: "template_field",
225
+ other_field: "other_field"
226
+
227
+ expected_config_fields = {"config_field" => "config_field",
228
+ "template_field" => "template_field"}
229
+ assert_equal expected_config_fields, config.defined_fields
230
+ end
231
+
232
+ test "launch calls after launch methods" do
233
+ config = AfterLaunchConfig.create
234
+ config.expects(:after_launch_method).with([config])
235
+ config.stubs(:enqueue_job)
236
+ config.launch
237
+ end
238
+
239
+ test "launch returns configurations" do
240
+ config = AfterLaunchConfig.create
241
+ AfterLaunchConfig.any_instance.stubs(:enqueue_job)
242
+ after_launch_configs = ["after launch 1", "after launch 2"]
243
+
244
+ config.stubs(:after_launch_method).returns(after_launch_configs)
245
+
246
+ configs = config.launch
247
+ assert_equal 3, configs.size
248
+
249
+ assert_equal [config] + after_launch_configs, configs
250
+ end
251
+
252
+ test "can create configuration with root object directly" do
253
+ root = Rootable.create
254
+ configuration = nil
255
+ assert_nothing_raised Mongoid::Errors::InvalidSetPolymorphicRelation do
256
+ configuration = TopConfig.create root_object: root
257
+ end
258
+
259
+ assert_equal root, configuration.root_object
260
+ assert_equal 1, root.configurations.size
261
+ assert_equal configuration, root.configurations.first
262
+
263
+ end
264
+
265
+ test "can create create config with root object directly" do
266
+ root = Rootable.create
267
+ config = TopConfig.create root_object: root
268
+
269
+ assert_equal 1, root.configurations.count
270
+ assert_equal config, root.configurations.first
271
+
272
+ root.reload
273
+ config.reload
274
+
275
+ assert_equal 1, root.configurations.count
276
+ assert_equal config, root.configurations.first
277
+
278
+ end
279
+
280
+ end
281
+ end