tim 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +17 -0
- data/README.rdoc +231 -8
- data/app/controllers/tim/application_controller.rb +6 -3
- data/app/controllers/tim/base_images_controller.rb +10 -18
- data/app/controllers/tim/image_versions_controller.rb +10 -9
- data/app/controllers/tim/provider_images_controller.rb +13 -10
- data/app/controllers/tim/target_images_controller.rb +9 -7
- data/app/controllers/tim/templates_controller.rb +7 -7
- data/app/exceptions/tim/exceptions.rb +6 -0
- data/app/filters/tim/resource_link_filter.rb +56 -0
- data/app/models/tim/base_image.rb +7 -3
- data/app/models/tim/image_version.rb +4 -2
- data/app/models/tim/provider_image.rb +42 -6
- data/app/models/tim/target_image.rb +48 -9
- data/app/models/tim/template.rb +18 -3
- data/app/patches/rails/active_record/autosave_association.rb +29 -0
- data/app/validators/template_validator.rb +9 -5
- data/app/views/tim/base_images/_base_image.xml.haml +1 -0
- data/app/views/tim/target_images/_target_image.xml.haml +1 -0
- data/db/migrate/20121115151914_add_import_to_tim_base_images.rb +5 -0
- data/db/migrate/20121210131423_add_build_method_to_target_image.rb +5 -0
- data/db/migrate/20121216131814_rename_provider_account_id_attribute.rb +13 -0
- data/db/migrate/20121216134538_add_factory_base_image_id_to_image_versions.rb +5 -0
- data/lib/image_factory/model/base_image.rb +6 -0
- data/lib/tim/engine.rb +12 -0
- data/lib/tim/version.rb +1 -1
- data/spec/controllers/base_images_controller_spec.rb +24 -8
- data/spec/controllers/image_versions_controller_spec.rb +20 -7
- data/spec/controllers/provider_images_controller_spec.rb +19 -6
- data/spec/controllers/target_images_controller_spec.rb +22 -8
- data/spec/factories/tim/base_image.rb +5 -1
- data/spec/factories/tim/image_factory/target_image.rb +1 -0
- data/spec/factories/tim/image_version.rb +4 -0
- data/spec/factories/tim/provider_image.rb +4 -1
- data/spec/factories/tim/target_image.rb +6 -2
- data/spec/filters/resource_link_filter_spec.rb +158 -0
- data/spec/models/base_image_spec.rb +5 -5
- data/spec/models/dummy/pool_family_spec.rb +1 -1
- data/spec/models/dummy/provider_account_spec.rb +3 -1
- data/spec/models/dummy/provider_type_spec.rb +2 -1
- data/spec/models/dummy/user_spec.rb +1 -1
- data/spec/models/image_version_spec.rb +4 -3
- data/spec/models/provider_image_spec.rb +28 -5
- data/spec/models/target_image_spec.rb +74 -4
- data/spec/models/template_spec.rb +11 -2
- data/spec/validators/template_validator_spec.rb +10 -0
- data/spec/views/base_images_spec.rb +2 -1
- data/spec/views/provider_images_spec.rb +1 -0
- data/test/dummy/app/decorators/tim/controllers/base_images_controller_decorator.rb +13 -0
- data/test/dummy/config/database.yml +0 -3
- data/test/dummy/config/initializers/tim.rb +1 -1
- data/test/dummy/config/routes.rb +6 -1
- data/test/dummy/db/migrate/20121216133232_add_provider_account_id_to_provider_images.rb +5 -0
- data/test/dummy/db/schema.rb +28 -24
- data/tim.gemspec +32 -0
- metadata +46 -60
- data/test/dummy/db/development.sqlite3 +0 -0
- data/test/dummy/db/test.sqlite3 +0 -0
- data/test/dummy/log/development.log +0 -982
- data/test/dummy/log/test.log +0 -8629
- data/test/dummy/tmp/cache/assets/C7A/BB0/sprockets%2F13445f7a19078dd2df39517062aa6711 +0 -0
- data/test/dummy/tmp/cache/assets/C8C/CC0/sprockets%2F95d79f3b3096348427f3e4e38b5202e3 +0 -0
- data/test/dummy/tmp/cache/assets/CB0/2B0/sprockets%2F79106b90879c02a115d7f6f1c8390ac4 +0 -0
- data/test/dummy/tmp/cache/assets/CE0/690/sprockets%2F04c628c2a636286bfa92a4966b82b92a +0 -0
- data/test/dummy/tmp/cache/assets/D03/040/sprockets%2Fd9e94204d4b307145f12efc109b16c1f +0 -0
- data/test/dummy/tmp/cache/assets/D06/DC0/sprockets%2Fcd282851b6e4c463409ba3ece67e0510 +0 -0
- data/test/dummy/tmp/cache/assets/D23/5F0/sprockets%2F2a521f3183c6bbcd71bd26a5490b201e +0 -0
- data/test/dummy/tmp/cache/assets/D64/3A0/sprockets%2F56ac1aed10c39b12a88cb1b30f668f58 +0 -0
- data/test/dummy/tmp/cache/assets/D69/BD0/sprockets%2F0aaf75cf34556b33a9fec534fe4d0415 +0 -0
- data/test/dummy/tmp/cache/assets/D87/D80/sprockets%2Fefa3c8b210e87358c7add88cd6f49597 +0 -0
- data/test/dummy/tmp/cache/assets/D89/200/sprockets%2Ff9b4e953c874ed6a87de6490d055d9db +0 -0
- data/test/dummy/tmp/cache/assets/DF4/430/sprockets%2F403bb1de60cae4325cebd7e6c389b8ad +0 -0
- data/test/dummy/tmp/cache/assets/E00/500/sprockets%2Faaddc5b6f2cb6b98930cc54cf4c64a95 +0 -0
- data/test/dummy/tmp/cache/assets/EB3/CE0/sprockets%2Fd75a89c9fffacd99bce7eed96844eafc +0 -0
@@ -2,26 +2,28 @@ module Tim
|
|
2
2
|
class TargetImagesController < Tim::ApplicationController
|
3
3
|
respond_to :json, :only => :update
|
4
4
|
|
5
|
+
prepend_before_filter ResourceLinkFilter.new({ :target_image => :image_version }),
|
6
|
+
:only => [:create]
|
5
7
|
before_filter :factory_keys, :only => :update
|
6
8
|
|
7
9
|
def index
|
8
10
|
@target_images = Tim::TargetImage.all unless defined? @target_images
|
9
|
-
respond_with @
|
11
|
+
respond_with(@target_images, @respond_options)
|
10
12
|
end
|
11
13
|
|
12
14
|
def show
|
13
15
|
@target_image = Tim::TargetImage.find(params[:id]) unless defined? @target_image
|
14
|
-
respond_with @
|
16
|
+
respond_with(@target_image, @respond_options)
|
15
17
|
end
|
16
18
|
|
17
19
|
def new
|
18
20
|
@target_image = Tim::TargetImage.new unless defined? @target_image
|
19
|
-
respond_with @
|
21
|
+
respond_with(@target_image, @respond_options)
|
20
22
|
end
|
21
23
|
|
22
24
|
def edit
|
23
25
|
@target_image = Tim::TargetImage.find(params[:id]) unless defined? @target_image
|
24
|
-
respond_with @
|
26
|
+
respond_with(@target_image, @respond_options)
|
25
27
|
end
|
26
28
|
|
27
29
|
def create
|
@@ -29,7 +31,7 @@ module Tim
|
|
29
31
|
if @target_image.save
|
30
32
|
flash[:notice] = 'Image version was successfully created.'
|
31
33
|
end
|
32
|
-
respond_with @
|
34
|
+
respond_with(@target_image, @respond_options)
|
33
35
|
end
|
34
36
|
|
35
37
|
def update
|
@@ -37,7 +39,7 @@ module Tim
|
|
37
39
|
if @target_image.update_attributes(params[:target_image])
|
38
40
|
flash[:notice] = 'Target image was successfully updated.'
|
39
41
|
end
|
40
|
-
respond_with @
|
42
|
+
respond_with(@target_image, @respond_options)
|
41
43
|
end
|
42
44
|
|
43
45
|
# DELETE /target_images/1
|
@@ -45,7 +47,7 @@ module Tim
|
|
45
47
|
def destroy
|
46
48
|
@target_image = Tim::TargetImage.find(params[:id]) unless defined? @target_image
|
47
49
|
@target_image.destroy
|
48
|
-
respond_with @
|
50
|
+
respond_with(@target_image, @respond_options)
|
49
51
|
end
|
50
52
|
|
51
53
|
# TODO Add factory permission check
|
@@ -3,22 +3,22 @@ module Tim
|
|
3
3
|
|
4
4
|
def index
|
5
5
|
@templates = Tim::Template.all unless defined? @templates
|
6
|
-
respond_with @
|
6
|
+
respond_with(@templates, @respond_options)
|
7
7
|
end
|
8
8
|
|
9
9
|
def show
|
10
10
|
@template = Tim::Template.find(params[:id]) unless defined? @template
|
11
|
-
respond_with @
|
11
|
+
respond_with(@template, @respond_options)
|
12
12
|
end
|
13
13
|
|
14
14
|
def new
|
15
15
|
@template = Tim::Template.new unless defined? @template
|
16
|
-
respond_with @
|
16
|
+
respond_with(@template, @respond_options)
|
17
17
|
end
|
18
18
|
|
19
19
|
def edit
|
20
20
|
@template = Tim::Template.find(params[:id])
|
21
|
-
respond_with @
|
21
|
+
respond_with(@template, @respond_options)
|
22
22
|
end
|
23
23
|
|
24
24
|
def create
|
@@ -26,7 +26,7 @@ module Tim
|
|
26
26
|
if @template.save
|
27
27
|
flash[:notice] = 'Template was successfully created.'
|
28
28
|
end
|
29
|
-
respond_with @
|
29
|
+
respond_with(@template, @respond_options)
|
30
30
|
end
|
31
31
|
|
32
32
|
def update
|
@@ -34,13 +34,13 @@ module Tim
|
|
34
34
|
if @template.update_attributes(params[:template])
|
35
35
|
flash[:notice] = 'Template was successfully updated.'
|
36
36
|
end
|
37
|
-
respond_with @
|
37
|
+
respond_with(@template, @respond_options)
|
38
38
|
end
|
39
39
|
|
40
40
|
def destroy
|
41
41
|
@template = Tim::Template.find(params[:id]) unless defined? @template
|
42
42
|
@template.destroy
|
43
|
-
respond_with @
|
43
|
+
respond_with(@template, @respond_options)
|
44
44
|
end
|
45
45
|
|
46
46
|
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# Before filter for controllers that handle REST API requests. Conductor uses
|
2
|
+
# linking to resources like <pool id='1'/> (resulting in :pool => { :id => 1 }
|
3
|
+
# in request params hash) but ActiveRecord expects linking to resources like
|
4
|
+
# <pool_id>1</pool_id> (resluting in :pool_id => 1 in request params hash).
|
5
|
+
#
|
6
|
+
# This before filter operates on request params hash to rewrite it from our
|
7
|
+
# format to ActiveRecord-friendly format.
|
8
|
+
#
|
9
|
+
# The constructor of the filter accepts specification of what should be
|
10
|
+
# transformed. E.g.:
|
11
|
+
#
|
12
|
+
# before_filter ResourceLinkFilter.new({ :catalog => :pool }),
|
13
|
+
# :only => [:create, :update]
|
14
|
+
#
|
15
|
+
# would transform { :catalog => { :pool => { :id => 1 }}}
|
16
|
+
# into { :catalog => { :pool_id => 1 }}
|
17
|
+
#
|
18
|
+
# See the specs in spec/util/resource_link_filter_spec.rb for more examples.
|
19
|
+
#
|
20
|
+
|
21
|
+
# NOTE This filter was taken from the aeolus conductor project: https://github.com/aeolusproject/conductor/
|
22
|
+
|
23
|
+
class Tim::ResourceLinkFilter
|
24
|
+
def initialize(resource_links)
|
25
|
+
@resource_links = resource_links
|
26
|
+
end
|
27
|
+
|
28
|
+
def before(controller)
|
29
|
+
return unless controller.request.format == :xml
|
30
|
+
|
31
|
+
transform_resource_links_recursively(controller.params, @resource_links)
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def transform_resource_links_recursively(subparams, sublinks)
|
38
|
+
return if subparams == nil
|
39
|
+
|
40
|
+
case sublinks
|
41
|
+
when Symbol # then transform the link (last level of recursion)
|
42
|
+
return if subparams[sublinks] == nil || subparams[sublinks][:id] == nil
|
43
|
+
|
44
|
+
subparams[:"#{sublinks}_id"] = subparams[sublinks][:id]
|
45
|
+
subparams.delete(sublinks)
|
46
|
+
when Array # then process each item
|
47
|
+
sublinks.each do |item|
|
48
|
+
transform_resource_links_recursively(subparams, item)
|
49
|
+
end
|
50
|
+
when Hash # then descend into each entry
|
51
|
+
sublinks.each_key do |key|
|
52
|
+
transform_resource_links_recursively(subparams[key], sublinks[key])
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -1,13 +1,17 @@
|
|
1
1
|
module Tim
|
2
2
|
class BaseImage < Tim::Base
|
3
|
-
has_many :image_versions
|
4
|
-
belongs_to :template
|
3
|
+
has_many :image_versions, :inverse_of => :base_image
|
4
|
+
belongs_to :template, :inverse_of => :base_images
|
5
|
+
|
5
6
|
belongs_to :user, :class_name => Tim.user_class
|
6
7
|
|
7
8
|
accepts_nested_attributes_for :template
|
8
9
|
accepts_nested_attributes_for :image_versions
|
9
10
|
|
10
|
-
|
11
|
+
validates_presence_of :name
|
12
|
+
validates_presence_of :template, :unless => :import
|
13
|
+
|
14
|
+
attr_accessible :template, :name, :description, :import
|
11
15
|
attr_accessible :template_attributes
|
12
16
|
attr_accessible :image_versions_attributes, :as => :admin
|
13
17
|
attr_protected :id
|
@@ -1,11 +1,13 @@
|
|
1
1
|
module Tim
|
2
2
|
class ImageVersion < Tim::Base
|
3
|
-
belongs_to :base_image
|
4
|
-
has_many :target_images
|
3
|
+
belongs_to :base_image, :inverse_of => :image_versions
|
4
|
+
has_many :target_images, :inverse_of => :image_version
|
5
5
|
|
6
6
|
accepts_nested_attributes_for :base_image
|
7
7
|
accepts_nested_attributes_for :target_images
|
8
8
|
|
9
|
+
validates_presence_of :base_image
|
10
|
+
|
9
11
|
attr_accessible :base_image_attributes
|
10
12
|
attr_accessible :target_images_versions_attributes
|
11
13
|
attr_protected :id
|
@@ -1,37 +1,63 @@
|
|
1
1
|
module Tim
|
2
2
|
class ProviderImage < Tim::Base
|
3
|
-
belongs_to :target_image
|
3
|
+
belongs_to :target_image, :inverse_of => :provider_images
|
4
4
|
belongs_to :provider_account, :class_name => Tim.provider_account_class
|
5
5
|
|
6
6
|
accepts_nested_attributes_for :target_image
|
7
7
|
|
8
8
|
attr_accessible :target_image_attributes
|
9
|
+
attr_accessible :external_image_id, :if => :imported?
|
9
10
|
attr_accessible :status, :status_detail, :progress #, :as => :image_factory
|
10
11
|
attr_accessible :provider
|
11
12
|
attr_writer :credentials
|
12
13
|
attr_protected :id
|
13
14
|
|
14
|
-
|
15
|
+
validates_presence_of :target_image
|
16
|
+
validates_presence_of :external_image_id, :if => :imported?
|
17
|
+
|
18
|
+
after_create :create_factory_provider_image, :unless => :imported?
|
19
|
+
after_create :create_import, :if => :imported?
|
20
|
+
|
21
|
+
def imported?
|
22
|
+
target_image.imported?
|
23
|
+
end
|
15
24
|
|
16
25
|
private
|
26
|
+
def factory_provider_credentials
|
27
|
+
@credentials
|
28
|
+
end
|
29
|
+
|
30
|
+
def factory_provider
|
31
|
+
self.provider
|
32
|
+
end
|
33
|
+
|
17
34
|
def create_factory_provider_image
|
18
35
|
begin
|
19
|
-
provider_image = ImageFactory::ProviderImage.new(:
|
20
|
-
:provider => self.provider,
|
21
|
-
:credentials => @credentials,
|
36
|
+
provider_image = ImageFactory::ProviderImage.new(:credentials => factory_provider_credentials,
|
22
37
|
# TODO Remove this when upgrading to 3.2
|
23
38
|
# target conflicts with rails 3.0.10
|
24
39
|
# ActiveRecord::Associations::Association#target
|
25
40
|
:target => target_image.target,
|
26
41
|
:parameters => "")
|
42
|
+
provider_image.provider = factory_provider
|
27
43
|
# TODO There is a bug in ARes 3.0.10 that will add map name twice when setting in mass assign. So we set
|
28
44
|
# parameters separately.
|
29
45
|
# Setting parameters at mass assign results in json => {"target_image":"parameters":{"parameters":{"..."}}}"
|
30
46
|
# This should be tested and removed if fixed in 3.2
|
31
47
|
provider_image.parameters = { :callbacks => ["#{ImageFactory::ProviderImage.callback_url}/#{self.id}"] }
|
48
|
+
if target_image.snapshot?
|
49
|
+
provider_image.parameters[:snapshot] = true
|
50
|
+
provider_image.template = self.target_image.template.xml
|
51
|
+
else
|
52
|
+
provider_image.target_image_id = self.target_image.factory_id
|
53
|
+
end
|
54
|
+
|
32
55
|
provider_image.save!
|
33
56
|
populate_factory_fields(provider_image)
|
34
57
|
self.save
|
58
|
+
rescue Errno::ECONNREFUSED
|
59
|
+
raise Tim::Error::ImagefactoryConnectionRefused.new("Unable to connect"\
|
60
|
+
" to Imagefactory server @ #{Tim::ImageFactory::Base.site}")
|
35
61
|
rescue => e
|
36
62
|
# TODO Add proper error handling
|
37
63
|
raise e
|
@@ -45,7 +71,17 @@ module Tim
|
|
45
71
|
self.progress = factory_provider_image.percent_complete
|
46
72
|
self.provider = factory_provider_image.provider
|
47
73
|
self.external_image_id = factory_provider_image.identifier_on_provider
|
48
|
-
self.
|
74
|
+
self.factory_provider_account_id = factory_provider_image.provider_account_identifier
|
75
|
+
end
|
76
|
+
|
77
|
+
# TODO At the moment this method simply sets fields to import defaults.
|
78
|
+
# We should investigate whether we can check the image exists and if the
|
79
|
+
# user can access it. Deltacloud?
|
80
|
+
def create_import
|
81
|
+
self.status = "COMPLETE"
|
82
|
+
self.progress = "COMPLETE"
|
83
|
+
self.status_detail = "Imported Image"
|
84
|
+
self.save
|
49
85
|
end
|
50
86
|
end
|
51
87
|
end
|
@@ -1,12 +1,15 @@
|
|
1
1
|
module Tim
|
2
2
|
class TargetImage < Tim::Base
|
3
|
-
belongs_to :image_version
|
3
|
+
belongs_to :image_version, :inverse_of => :target_images
|
4
|
+
has_many :provider_images, :inverse_of => :target_image
|
5
|
+
|
4
6
|
belongs_to :provider_type, :class_name => Tim.provider_type_class
|
5
|
-
has_many :provider_images
|
6
7
|
|
7
8
|
accepts_nested_attributes_for :image_version
|
8
9
|
accepts_nested_attributes_for :provider_images
|
9
10
|
|
11
|
+
validates_presence_of :image_version, :target
|
12
|
+
|
10
13
|
attr_accessible :image_version_attributes
|
11
14
|
attr_accessible :provider_images_attributes
|
12
15
|
attr_accessible :status, :status_detail, :progress #, :as => :image_factory
|
@@ -14,25 +17,42 @@ module Tim
|
|
14
17
|
|
15
18
|
attr_protected :id
|
16
19
|
|
17
|
-
after_create :create_factory_target_image
|
20
|
+
after_create :create_factory_target_image, :if => :create_factory_target_image?
|
21
|
+
after_create :set_import_snapshot_status, :if => lambda { |t| t.imported? || t.snapshot? }
|
18
22
|
|
19
23
|
def template
|
20
24
|
image_version.base_image.template
|
21
25
|
end
|
22
26
|
|
27
|
+
def imported?
|
28
|
+
image_version.base_image.import
|
29
|
+
end
|
30
|
+
|
31
|
+
def snapshot?
|
32
|
+
build_method == "SNAPSHOT"
|
33
|
+
end
|
34
|
+
|
23
35
|
private
|
24
36
|
def create_factory_target_image
|
25
37
|
begin
|
26
|
-
target_image = ImageFactory::TargetImage.new(:
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
# when a map is provided in mass assign
|
38
|
+
target_image = ImageFactory::TargetImage.new(:target => target,
|
39
|
+
:parameters => nil)
|
40
|
+
# A bug in ARes adds parameters twice to the resulting json when a map
|
41
|
+
# is provided in mass assign
|
31
42
|
target_image.parameters = { :callbacks => ["#{ImageFactory::TargetImage.callback_url}/#{self.id}"] }
|
43
|
+
|
44
|
+
if image_version.factory_base_image_id
|
45
|
+
target_image.base_image_id = image_version.factory_base_image_id
|
46
|
+
else
|
47
|
+
target_image.template = template.xml
|
48
|
+
end
|
32
49
|
target_image.save!
|
33
50
|
|
34
51
|
populate_factory_fields(target_image)
|
35
52
|
self.save
|
53
|
+
rescue Errno::ECONNREFUSED
|
54
|
+
raise Tim::Error::ImagefactoryConnectionRefused.new("Unable to connect"\
|
55
|
+
" to Imagefactory server @ #{Tim::ImageFactory::Base.site}")
|
36
56
|
rescue => e
|
37
57
|
# TODO Add proper error handling
|
38
58
|
raise e
|
@@ -44,7 +64,26 @@ module Tim
|
|
44
64
|
self.factory_id = factory_target_image.id
|
45
65
|
self.status_detail = factory_target_image.status_detail.activity
|
46
66
|
self.progress = factory_target_image.percent_complete
|
67
|
+
unless self.image_version.factory_base_image_id
|
68
|
+
image_version.factory_base_image_id = factory_target_image.base_image_id
|
69
|
+
image_version.save!
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def set_import_snapshot_status
|
74
|
+
self.progress = "COMPLETE"
|
75
|
+
self.status = "COMPLETE"
|
76
|
+
if self.imported?
|
77
|
+
self.status_detail = "Imported Image"
|
78
|
+
elsif self.snapshot?
|
79
|
+
self.status_detail = "Snapshot Image"
|
80
|
+
end
|
81
|
+
self.save
|
47
82
|
end
|
48
83
|
|
84
|
+
private
|
85
|
+
def create_factory_target_image?
|
86
|
+
!imported? && !snapshot?
|
87
|
+
end
|
49
88
|
end
|
50
|
-
end
|
89
|
+
end
|
data/app/models/tim/template.rb
CHANGED
@@ -5,15 +5,30 @@ module Tim
|
|
5
5
|
include ActiveModel::Validations
|
6
6
|
validates_with TemplateValidator
|
7
7
|
|
8
|
-
has_many :base_images
|
8
|
+
has_many :base_images, :inverse_of => :template
|
9
9
|
|
10
10
|
attr_accessible :xml
|
11
11
|
attr_protected :id
|
12
12
|
|
13
|
+
OS = Struct.new(:name, :version, :arch)
|
14
|
+
|
13
15
|
# Used in views to display the xml elements of this template
|
14
16
|
def xml_elements
|
15
|
-
|
17
|
+
parsed_xml.xpath("//template/*").to_xml
|
18
|
+
end
|
19
|
+
|
20
|
+
def os
|
21
|
+
OS.new(
|
22
|
+
parsed_xml.xpath("//template/os/name").text,
|
23
|
+
parsed_xml.xpath("//template/os/version").text,
|
24
|
+
parsed_xml.xpath("//template/os/arch").text
|
25
|
+
)
|
16
26
|
end
|
17
27
|
|
28
|
+
private
|
29
|
+
|
30
|
+
def parsed_xml
|
31
|
+
@parsed_xml ||= ::Nokogiri::XML::Document.parse(xml)
|
32
|
+
end
|
18
33
|
end
|
19
|
-
end
|
34
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# There is a bug in Rails which causes issues when using accepts_nested_attrs
|
2
|
+
# and inverse_of on associations: https://github.com/rails/rails/issues/7809/
|
3
|
+
# This code was lifted from the pull request of the bug shown above. It solves
|
4
|
+
# the issue. Until this is merged into rails we will have to carry the patch
|
5
|
+
# ourselves.
|
6
|
+
|
7
|
+
module ActiveRecord
|
8
|
+
module AutosaveAssociation
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
# Returns whether or not this record has been changed in any way (including whether
|
12
|
+
# any of its nested autosave associations are likewise changed)
|
13
|
+
def changed_for_autosave?
|
14
|
+
@_changed_for_autosave_called ||= false
|
15
|
+
if @_changed_for_autosave_called
|
16
|
+
# traversing a cyclic graph of objects; stop it
|
17
|
+
result = false
|
18
|
+
else
|
19
|
+
begin
|
20
|
+
@_changed_for_autosave_called = true
|
21
|
+
result = new_record? || changed? || marked_for_destruction? || nested_records_changed_for_autosave?
|
22
|
+
ensure
|
23
|
+
@_changed_for_autosave_called = false
|
24
|
+
end
|
25
|
+
end
|
26
|
+
result
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|