infopark_fiona7 0.30.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +3 -0
  3. data/Rakefile +21 -0
  4. data/app/assets/images/fiona7-logo.png +0 -0
  5. data/app/assets/images/fiona7-marker.png +0 -0
  6. data/app/assets/javascripts/fiona7.js +284 -0
  7. data/app/assets/stylesheets/fiona7-login.css.scss +15 -0
  8. data/app/assets/stylesheets/fiona7.css.scss +83 -0
  9. data/app/controllers/fiona7/blobs_controller.rb +35 -0
  10. data/app/controllers/fiona7/default_scrivito_cms_controller.rb +16 -0
  11. data/app/controllers/fiona7/release_controller.rb +24 -0
  12. data/app/controllers/fiona7/sessions_controller.rb +49 -0
  13. data/app/controllers/fiona7_login_page_controller.rb +27 -0
  14. data/app/helpers/fiona7_login_helper.rb +18 -0
  15. data/app/helpers/fiona7_override_helper.rb +47 -0
  16. data/app/models/fiona7/edited_obj.rb +15 -0
  17. data/app/models/fiona7/internal_released_obj.rb +18 -0
  18. data/app/models/fiona7/released_obj.rb +18 -0
  19. data/app/models/fiona7/write_obj.rb +63 -0
  20. data/app/models/fiona7_login_page.rb +5 -0
  21. data/app/models/x_container.rb +5 -0
  22. data/app/models/x_generic.rb +5 -0
  23. data/app/models/x_image.rb +5 -0
  24. data/app/views/fiona7_login_page/index.html.erb +14 -0
  25. data/app/views/layouts/fiona7_login_page_layout.html.erb +23 -0
  26. data/config/locales/login.yml +19 -0
  27. data/config/locales/permissions.yml +7 -0
  28. data/config/routes.rb +9 -0
  29. data/lib/fiona7/assert.rb +17 -0
  30. data/lib/fiona7/builder/batch_widget_writer.rb +107 -0
  31. data/lib/fiona7/builder/obj_builder.rb +297 -0
  32. data/lib/fiona7/builder/obj_class_builder.rb +111 -0
  33. data/lib/fiona7/builder/obj_class_updater.rb +69 -0
  34. data/lib/fiona7/builder/obj_updater.rb +25 -0
  35. data/lib/fiona7/builder/widget_builder.rb +13 -0
  36. data/lib/fiona7/builder/widget_building.rb +35 -0
  37. data/lib/fiona7/builder/widget_updater.rb +13 -0
  38. data/lib/fiona7/controllers/content_service/obj_controller.rb +125 -0
  39. data/lib/fiona7/controllers/content_service/workspace_controller.rb +19 -0
  40. data/lib/fiona7/controllers/rest_api/blob_controller.rb +11 -0
  41. data/lib/fiona7/controllers/rest_api/obj_class_controller.rb +39 -0
  42. data/lib/fiona7/controllers/rest_api/obj_controller.rb +175 -0
  43. data/lib/fiona7/controllers/rest_api/workspace_controller.rb +58 -0
  44. data/lib/fiona7/engine.rb +138 -0
  45. data/lib/fiona7/fiona_connector_patches/basic_obj.rb +9 -0
  46. data/lib/fiona7/initializer.rb +78 -0
  47. data/lib/fiona7/json/obj_class_decorator.rb +45 -0
  48. data/lib/fiona7/json/obj_decorator.rb +153 -0
  49. data/lib/fiona7/json/typeless_obj_decorator.rb +30 -0
  50. data/lib/fiona7/json/widget_decorator.rb +18 -0
  51. data/lib/fiona7/link_converter/fiona_to_scrivito.rb +37 -0
  52. data/lib/fiona7/link_converter/scrivito_to_fiona.rb +42 -0
  53. data/lib/fiona7/mode_switch.rb +7 -0
  54. data/lib/fiona7/mode_switch/abstract_mode_switch.rb +19 -0
  55. data/lib/fiona7/mode_switch/cms_routes.rb +33 -0
  56. data/lib/fiona7/mode_switch/cms_routes/fiona_connector.rb +17 -0
  57. data/lib/fiona7/mode_switch/cms_routes/scrivito_sdk.rb +20 -0
  58. data/lib/fiona7/mode_switch/cms_routes/scrivito_sdk_slave.rb +20 -0
  59. data/lib/fiona7/mode_switch/composite.rb +33 -0
  60. data/lib/fiona7/mode_switch/constants.rb +86 -0
  61. data/lib/fiona7/mode_switch/views.rb +37 -0
  62. data/lib/fiona7/naive_search_engine.rb +111 -0
  63. data/lib/fiona7/recursive_link_resolver.rb +93 -0
  64. data/lib/fiona7/routers/content_service.rb +19 -0
  65. data/lib/fiona7/routers/rest_api.rb +135 -0
  66. data/lib/fiona7/routing_monkey_patch.rb +33 -0
  67. data/lib/fiona7/scrivito_patches/attribute_content.rb +19 -0
  68. data/lib/fiona7/scrivito_patches/basic_obj.rb +92 -0
  69. data/lib/fiona7/scrivito_patches/basic_widget.rb +14 -0
  70. data/lib/fiona7/scrivito_patches/binary.rb +55 -0
  71. data/lib/fiona7/scrivito_patches/client_config.rb +19 -0
  72. data/lib/fiona7/scrivito_patches/cms_dispatch_controller.rb +31 -0
  73. data/lib/fiona7/scrivito_patches/cms_rest_api.rb +11 -0
  74. data/lib/fiona7/scrivito_patches/cms_routing.rb +66 -0
  75. data/lib/fiona7/scrivito_patches/controller_actions.rb +14 -0
  76. data/lib/fiona7/scrivito_patches/link_parser.rb +22 -0
  77. data/lib/fiona7/scrivito_patches/migrator.rb +13 -0
  78. data/lib/fiona7/scrivito_patches/obj_class.rb +16 -0
  79. data/lib/fiona7/scrivito_patches/type_computer.rb +25 -0
  80. data/lib/fiona7/scrivito_patches/workspace.rb +12 -0
  81. data/lib/fiona7/scrivito_user.rb +44 -0
  82. data/lib/fiona7/search_engine.rb +20 -0
  83. data/lib/fiona7/type_register.rb +140 -0
  84. data/lib/fiona7/type_register_mixin.rb +9 -0
  85. data/lib/fiona7/version.rb +3 -0
  86. data/lib/fiona7/version_helper.rb +84 -0
  87. data/lib/fiona7/widget_resolver.rb +37 -0
  88. data/lib/fiona7/workspace.rb +76 -0
  89. data/lib/infopark_fiona7.rb +4 -0
  90. data/lib/tasks/fiona7_tasks.rake +13 -0
  91. metadata +216 -0
@@ -0,0 +1,297 @@
1
+ module Fiona7
2
+ module Builder
3
+ class ObjBuilder
4
+ end
5
+ end
6
+ end
7
+
8
+ require 'fiona7/type_register_mixin'
9
+ require 'fiona7/recursive_link_resolver'
10
+ #require 'fiona7/write_obj'
11
+ #require 'fiona7/released_obj'
12
+ require 'fiona7/builder/batch_widget_writer'
13
+ require 'fiona7/link_converter/scrivito_to_fiona'
14
+ require 'fiona7/widget_resolver'
15
+
16
+
17
+ module Fiona7
18
+ module Builder
19
+ class ObjBuilder
20
+ include TypeRegisterMixin
21
+
22
+ def initialize(values)
23
+ @values = values.symbolize_keys
24
+ end
25
+
26
+ def build
27
+ assert_valid
28
+ prepare_object
29
+
30
+ create_object
31
+ write_widget_pool
32
+ namespace_attributes
33
+ store_attributes
34
+
35
+ @obj
36
+ end
37
+
38
+ def validate
39
+ true
40
+ end
41
+
42
+ protected
43
+ def assert_valid
44
+ validate || (raise Scrivito::ClientError.new(@error_msg || "Invalid input", 422))
45
+ end
46
+
47
+ def prepare_object
48
+ @path = @values.delete(:_path)
49
+ @obj_class = @values.delete(:_obj_class)
50
+ @widget_pool = @values.delete(:_widget_pool)
51
+ @permalink = @values.delete(:_permalink)
52
+ if @path
53
+ @path = "/#{@path}" unless @path.start_with?('/')
54
+ @name, parent_path = name_and_parent_path_from_path(@path)
55
+ end
56
+ @parent = ensure_parent_exists(parent_path) if parent_path
57
+ end
58
+
59
+ def namespace_attributes
60
+ # nop
61
+ end
62
+
63
+ def create_object
64
+ # bottom one is faster, but can only work when there are no widgets and no uploads
65
+ if (@widget_pool && !@widget_pool.empty?) || (@values.any? {|_, v| v.kind_of?(File) || v.kind_of?(ActionDispatch::Http::UploadedFile) } )
66
+ @obj = WriteObj.create!(name: @name, parent_obj_id: @parent.id, obj_class: @obj_class)
67
+ else
68
+ @obj = WriteObj.new(name: @name, parent_obj_id: @parent.id, obj_class: @obj_class)
69
+ end
70
+ end
71
+
72
+ def write_widget_pool
73
+ resolver = WidgetResolver.new(@obj[:X_widget_pool]||[])
74
+
75
+ @id_map = resolver.id_map
76
+ @widget_path_map = resolver.path_map
77
+
78
+ if @widget_pool && !@widget_pool.empty?
79
+ @widgets = BatchWidgetWriter.new(@obj, @id_map, @widget_pool, @widget_path_map)
80
+ @widgets.write
81
+
82
+ # FIXME: refactor this code
83
+ if @widgets.pool_changed?
84
+ # after widget writing this either has new widgets
85
+ # or some widgets have been removed
86
+ @new_widget_pool = @widget_path_map.map do |widget_id, path|
87
+ {title: widget_id, destination_object: path}
88
+ end
89
+ end
90
+ end
91
+ end
92
+
93
+ def store_attributes
94
+ if @obj.obj_class != @obj_class
95
+ @obj.obj_class = @obj_class
96
+ @obj.save!
97
+ @obj.reload
98
+ end
99
+ # TODO: reload into singleton class
100
+ @obj.send(:reload_attributes)
101
+ @values.each do |attribute_name, value|
102
+ virtual_name = attribute_name.to_sym
103
+ attribute_name = real_name = type_register.real_attribute_name(@obj.obj_class, virtual_name)
104
+ virtual_type = type_register.attribute_type(@obj.obj_class, virtual_name)
105
+
106
+ case virtual_type
107
+ when :linklist
108
+ links = (value || []).map do |link|
109
+ link = link.symbolize_keys
110
+ new_link = {}
111
+
112
+ if link[:url]
113
+ # handle www.example.com
114
+ new_link[:url] = "external:#{link[:url]}" unless link[:url] =~ /\A[a-zA-Z][a-zA-Z0-9+.-]:/
115
+ elsif obj_id = link[:obj_id]
116
+ new_link[:url] = WriteObj.find(obj_id).path
117
+ end
118
+
119
+
120
+ new_link[:url] ||= link[:url]
121
+ new_link[:title] = link[:title] if link[:title]
122
+ new_link[:target] = link[:target] if link[:target]
123
+ new_link[:url] = "#{new_link[:url]}?#{link[:query]}" if link[:query]
124
+ new_link[:url] = "#{new_link[:url]}##{link[:fragment]}" if link[:fragment]
125
+
126
+ new_link
127
+ end
128
+ @obj.set(attribute_name.to_s, links)
129
+ when :link
130
+ link = (value || {}).symbolize_keys
131
+ new_link = {}
132
+
133
+ if link[:url]
134
+ # handle www.example.com
135
+ new_link[:url] = "external:#{link[:url]}" unless link[:url] =~ /\A[a-zA-Z][a-zA-Z0-9+.-]:/
136
+ elsif obj_id = link[:obj_id]
137
+ new_link[:url] = WriteObj.find(obj_id).path
138
+ end
139
+
140
+
141
+ new_link[:url] ||= link[:url]
142
+ new_link[:title] = link[:title] if link[:title]
143
+ new_link[:target] = link[:target] if link[:target]
144
+ new_link[:url] = "#{new_link[:url]}?#{link[:query]}" if link[:query]
145
+ new_link[:url] = "#{new_link[:url]}##{link[:fragment]}" if link[:fragment]
146
+
147
+ new_link
148
+ if link.empty?
149
+ @obj.set(attribute_name.to_s, [])
150
+ else
151
+ @obj.set(attribute_name.to_s, [new_link])
152
+ end
153
+ when :reference
154
+ obj = WriteObj.find(value) rescue nil
155
+ if obj
156
+ @obj.set(attribute_name.to_s, [obj.path])
157
+ else
158
+ @obj.set(attribute_name.to_s, [])
159
+ end
160
+ when :referencelist
161
+ ids = value || []
162
+ if ids.empty?
163
+ @obj.set(attribute_name.to_s, [])
164
+ else
165
+ objects_map = Hash[WriteObj.where(obj_id: ids).map {|o| [o.id, o]}]
166
+ objects = ids.map {|id| objects_map[id] }.compact
167
+ @obj.set(attribute_name, objects)
168
+ end
169
+ when :widget
170
+ links = []
171
+ value = (value || {}).symbolize_keys
172
+ (value[:list] || []).each do |crap|
173
+ widget_id = (crap[:widget] || crap['widget']).to_sym
174
+
175
+ if (path=@widget_path_map[widget_id]||@widget_path_map[widget_id.to_s])
176
+ links << {destination_object: path, title: widget_id}
177
+ else
178
+ raise Scrivito::ScrivitoError, "Inconsistent widget pool state detected, unable to store widgets"
179
+ end
180
+ end
181
+ @obj.set(attribute_name.to_s, links)
182
+ when :text, :string, :enum, :multienum, :date
183
+ @obj.set(attribute_name.to_s, value)
184
+ when :html
185
+ converted_links = LinkConverter::ScrivitoToFiona.new(WriteObj, value.to_s).convert
186
+ @obj.set(attribute_name.to_s, converted_links)
187
+ when :binary
188
+ if value.kind_of?(String)
189
+ # NOTE: this code path has not been tested yet.
190
+ target = {title: value, destination_object: ReleasedObj.find(value.to_i)}
191
+ @obj.set(attribute_name.to_s, target)
192
+ elsif value.kind_of?(File)
193
+ @obj.set(attribute_name.to_s, upload_file(value))
194
+ elsif value.kind_of?(ActionDispatch::Http::UploadedFile)
195
+ @obj.set(attribute_name.to_s, upload_uploaded_file(value))
196
+ elsif value.nil?
197
+ else
198
+ raise Scrivito::ClientError.new("Invalid input for binary field", 422)
199
+ end
200
+ else
201
+ raise Scrivito::ClientError.new("Unknown attribute type: #{virtual_type}", 418)
202
+ end
203
+ end
204
+ @obj.set(:X_widget_pool, @new_widget_pool) if @new_widget_pool
205
+ @obj.set(:permalink, @permalink) if @permalink
206
+ @obj.save!
207
+
208
+ @obj.edit! unless @obj.really_edited?
209
+ end
210
+
211
+ def upload_file(file)
212
+ # only publication type can contain children (code = "5")
213
+ # also handle / this way
214
+ if @obj.obj_type_code != "5" || @obj.id == 2001
215
+ parent_path = "/_uploads/#{@obj.id}"
216
+ else
217
+ parent_path = "#{@obj.path}/_uploads"
218
+ end
219
+
220
+ parent = ensure_parent_exists(parent_path)
221
+ ext = ::File.extname(file.path).to_s[1..-1]
222
+ name = ::File.basename(file.path, '.' + ext.to_s)
223
+ obj_class = if ['jpg', 'jpeg', 'gif', 'png', 'tif', 'tiff'].include?(ext)
224
+ 'X_Image'
225
+ else
226
+ 'X_Generic'
227
+ end
228
+
229
+ raise Scrivito::ScrivitoError.new("File extension of uploaded file cannot be identified", 422) if ext.nil?
230
+
231
+ upload = WriteObj.upload(file, ext, {name: name, parent: parent, obj_class: obj_class}).tap(&:release!)
232
+ encoded_id = '%032d' % upload.id
233
+ {title: encoded_id, destination_object: upload}
234
+ end
235
+
236
+ def upload_uploaded_file(file)
237
+ # FIXME: DRY
238
+ # only publication type can contain children (code = "5")
239
+ # also handle / this way
240
+ if @obj.obj_type_code != "5" || @obj.id == 2001
241
+ parent_path = "/_uploads/#{@obj.id}"
242
+ else
243
+ parent_path = "#{@obj.path}/_uploads"
244
+ end
245
+
246
+ parent = ensure_parent_exists(parent_path)
247
+ # here the first difference
248
+ ext = ::File.extname(file.original_filename).to_s[1..-1]
249
+ name = ::File.basename(file.original_filename, '.' + ext.to_s)
250
+ obj_class = if ['jpg', 'jpeg', 'gif', 'png', 'tif', 'tiff'].include?(ext)
251
+ 'X_Image'
252
+ else
253
+ 'X_Generic'
254
+ end
255
+
256
+ raise Scrivito::ScrivitoError.new("File extension of uploaded file cannot be identified", 422) if ext.nil?
257
+
258
+ # here the second difference
259
+ upload = WriteObj.upload(file.open, ext, {name: name, parent: parent, obj_class: obj_class}).tap(&:release!)
260
+ encoded_id = '%032d' % upload.id
261
+ {title: encoded_id, destination_object: upload}
262
+ end
263
+
264
+
265
+ def name_and_parent_path_from_path(path)
266
+ components = path.split('/')
267
+ name = components.pop.presence
268
+ parent_path= components.join('/').presence || '/'
269
+
270
+ return name, parent_path
271
+ end
272
+
273
+ def ensure_parent_exists(path)
274
+ remaining = path.split("/")
275
+ current = []
276
+ paths = []
277
+ original = path
278
+
279
+ while !remaining.empty?
280
+ current.push(remaining.shift)
281
+
282
+ path = current.join('/').presence || '/'
283
+ paths.push(path)
284
+ end
285
+
286
+ paths.each do |path|
287
+ if !WriteObj.exists?(path: path)
288
+ name, parent_path = name_and_parent_path_from_path(path)
289
+ WriteObj.create!(name: name, parent_obj_id: WriteObj.find_by_path(parent_path).id, obj_class: 'X_Container')
290
+ end
291
+ end
292
+
293
+ WriteObj.find_by_path(original)
294
+ end
295
+ end
296
+ end
297
+ end
@@ -0,0 +1,111 @@
1
+ require 'fiona7/type_register_mixin'
2
+
3
+ module Fiona7
4
+ module Builder
5
+ class ObjClassBuilder
6
+ include TypeRegisterMixin
7
+
8
+ def initialize(values)
9
+ @values = values
10
+ @attrs_avail= @values.key?(:attributes)
11
+ @attributes = @values.delete(:attributes) {|_| [] }
12
+ end
13
+
14
+ def build
15
+ assert_valid
16
+ add_legacy_attributes
17
+ mode_dependant_attributes
18
+ namespace_attributes
19
+ persist_attributes
20
+ persist_obj_class
21
+ update_obj_class
22
+ load_cm_obj_class
23
+ end
24
+
25
+ def validate
26
+ unique_obj_class
27
+ end
28
+
29
+ protected
30
+ def assert_valid
31
+ validate || (raise (@error_msg || "Invalid input"))
32
+ end
33
+
34
+ def unique_obj_class
35
+ ret = @values[:name].present? && !RailsConnector::ObjClass.exists?(obj_class_name: @values[:name])
36
+ @error_msg = "Expected the ObjClass not to exist: #{@values[:name]}" unless ret
37
+ ret
38
+ end
39
+
40
+ def add_legacy_attributes
41
+ type = @values[:type].to_s
42
+ if type == 'generic' || type == 'image'
43
+ @attributes << {name: 'blob', type: :binary}
44
+ @attrs_avail = true
45
+ end
46
+ if type.present?
47
+ # legacy obj class
48
+ @attributes << {name: 'body', type: :html}
49
+ @attributes << {name: 'title', type: :html}
50
+ @attrs_avail = true
51
+ end
52
+ end
53
+
54
+ def mode_dependant_attributes
55
+ if Fiona7.mode == :standalone
56
+ # nop
57
+ elsif Fiona7.mode == :legacy
58
+ # in legacy mode body and title are always included
59
+ # and are mapped to built-in attributes, hence no
60
+ # need for custom attributes
61
+ @attributes.delete_if {|a| n = a[:name].to_s ; n == 'body' || n == 'title' }
62
+ else
63
+ raise "Invalid Fiona7.mode = #{Fiona7.mode}"
64
+ end
65
+
66
+ end
67
+
68
+ def namespace_attributes
69
+ @attributes.each do |attribute|
70
+ # s_OBJ_CLASS__ATTRIBUTE_NAME
71
+ virtual_name = attribute[:name]
72
+ attribute[:name] = type_register.real_attribute_name(@values[:name], virtual_name)
73
+ attribute[:real_type] = type_register.map_virtual_to_real_type(attribute[:type])
74
+ end
75
+ end
76
+
77
+ def persist_attributes
78
+ @attributes.each do |attribute|
79
+ if !RailsConnector::Attribute.exists?(attribute_name: attribute[:name])
80
+ raw_attribute = Reactor::Cm::Attribute.create(attribute[:name].to_s, attribute[:real_type].to_s)
81
+ raw_attribute.set(:helpText, ::ActiveSupport::JSON.encode({type: attribute[:type]}))
82
+ raw_attribute.set(:values, attribute[:values]) if attribute[:values]
83
+ raw_attribute.save!
84
+ end
85
+ end
86
+ end
87
+
88
+ def persist_obj_class
89
+ # WARNING: in the never version no "type" is standard
90
+ @values[:type] ||= 'publication'
91
+ @raw_obj_class = Reactor::Cm::ObjClass.create(@values[:name].to_s, @values[:type].to_s)
92
+ user_attributes = @attributes.map {|a| a[:name] } if @attrs_avail
93
+ user_attributes ||= []
94
+ built_in_attributes = ['X_widget_pool']
95
+ @raw_obj_class.attributes = user_attributes + built_in_attributes
96
+ end
97
+
98
+ def update_obj_class
99
+ if @values.key?(:is_active)
100
+ @raw_obj_class.set('isEnabled', @values[:is_active] ? '1' : '0')
101
+ @raw_obj_class.save!
102
+ end
103
+ end
104
+
105
+ def load_cm_obj_class
106
+ RailsConnector::ObjClass.find_by_obj_class_name(@values[:name])
107
+ end
108
+ end
109
+ end
110
+ end
111
+
@@ -0,0 +1,69 @@
1
+ require 'fiona7/builder/obj_class_builder'
2
+
3
+ module Fiona7
4
+ module Builder
5
+ class ObjClassUpdater < ObjClassBuilder
6
+ def validate
7
+ existing_obj_class
8
+ end
9
+
10
+ def build
11
+ assert_valid
12
+ mode_dependant_attributes
13
+ namespace_attributes
14
+ persist_attributes
15
+ persist_obj_class
16
+ update_obj_class
17
+ gc_attributes
18
+ load_cm_obj_class
19
+ end
20
+
21
+ protected
22
+ def existing_obj_class
23
+ ret = RailsConnector::ObjClass.exists?(obj_class_name: @values[:name])
24
+ @error_msg = "Expected #{@values[:name]} to exist" unless ret
25
+ ret
26
+ end
27
+
28
+ def persist_obj_class
29
+ @raw_obj_class = Reactor::Cm::ObjClass.get(@values[:name].to_s)
30
+
31
+ # DRY: ObjClassBuilder
32
+ if @attrs_avail
33
+ user_attributes = @attributes.map {|a| a[:name] }
34
+ user_attributes ||= []
35
+ else
36
+ user_attributes = @raw_obj_class.attributes
37
+ end
38
+
39
+ built_in_attributes = ['X_widget_pool']
40
+ @raw_obj_class.attributes = user_attributes + built_in_attributes
41
+ end
42
+
43
+ def persist_attributes
44
+ @attributes.each do |attribute|
45
+ # TODO: changing attribute types
46
+ if !RailsConnector::Attribute.exists?(attribute_name: attribute[:name])
47
+ raw_attribute = Reactor::Cm::Attribute.create(attribute[:name].to_s, attribute[:real_type].to_s)
48
+ raw_attribute.set(:helpText, ::ActiveSupport::JSON.encode({type: attribute[:type]}))
49
+ raw_attribute.set(:values, attribute[:values]) if attribute[:values]
50
+ raw_attribute.save!
51
+ end
52
+ end
53
+ end
54
+
55
+ def gc_attributes
56
+ return if Fiona7.mode == :legacy
57
+ return unless @attrs_avail
58
+ pattern = "s_#{@values[:name]}__%"
59
+
60
+ fresh = @attributes.map {|a| a[:name].to_s }
61
+ stale = RailsConnector::Attribute.where('attribute_name LIKE ?', pattern).select(:attribute_name).map(&:name)
62
+
63
+ (stale - fresh).each do |attribute_name|
64
+ Reactor::Cm::Attribute.instance(attribute_name.to_s).delete!
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end