infopark_fiona7 1.2.0.0.1 → 1.2.0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/scrivito_patches/models/obj.js +2 -6
  3. data/infopark_fiona7.gemspec +1 -1
  4. data/lib/fiona7/attribute_readers/attribute_reader.rb +17 -0
  5. data/lib/fiona7/attribute_readers/binary_as_binary.rb +12 -0
  6. data/lib/fiona7/attribute_readers/binary_as_linklist.rb +26 -0
  7. data/lib/fiona7/attribute_readers/date_as_date.rb +18 -0
  8. data/lib/fiona7/attribute_readers/factory.rb +91 -0
  9. data/lib/fiona7/attribute_readers/helpers/html_deserializer.rb +21 -0
  10. data/lib/fiona7/attribute_readers/helpers/json_deserializer.rb +11 -0
  11. data/lib/fiona7/attribute_readers/helpers/link_deserializer.rb +32 -0
  12. data/lib/fiona7/attribute_readers/html_as_html.rb +14 -0
  13. data/lib/fiona7/attribute_readers/link_as_linklist.rb +13 -0
  14. data/lib/fiona7/attribute_readers/linklist_as_linklist.rb +15 -0
  15. data/lib/fiona7/attribute_readers/multienum_as_multienum.rb +12 -0
  16. data/lib/fiona7/attribute_readers/multienum_as_text.rb +14 -0
  17. data/lib/fiona7/attribute_readers/number_as_string.rb +11 -0
  18. data/lib/fiona7/attribute_readers/reference_as_linklist.rb +14 -0
  19. data/lib/fiona7/attribute_readers/reference_as_string.rb +11 -0
  20. data/lib/fiona7/attribute_readers/referencelist_as_linklist.rb +16 -0
  21. data/lib/fiona7/attribute_readers/referencelist_as_text.rb +14 -0
  22. data/lib/fiona7/attribute_readers/simple.rb +18 -0
  23. data/lib/fiona7/attribute_readers/stringlist_as_text.rb +18 -0
  24. data/lib/fiona7/attribute_readers/widgetlist_as_linklist.rb +33 -0
  25. data/lib/fiona7/attribute_type_mapper.rb +76 -0
  26. data/lib/fiona7/attribute_writers/attribute_writer.rb +16 -0
  27. data/lib/fiona7/attribute_writers/binary_as_binary.rb +74 -0
  28. data/lib/fiona7/attribute_writers/binary_as_linklist.rb +86 -0
  29. data/lib/fiona7/attribute_writers/date_as_date.rb +11 -0
  30. data/lib/fiona7/attribute_writers/factory.rb +90 -0
  31. data/lib/fiona7/attribute_writers/helpers/html_serializer.rb +21 -0
  32. data/lib/fiona7/attribute_writers/helpers/json_serializer.rb +11 -0
  33. data/lib/fiona7/attribute_writers/helpers/link_serializer.rb +37 -0
  34. data/lib/fiona7/attribute_writers/html_as_html.rb +12 -0
  35. data/lib/fiona7/attribute_writers/link_as_linklist.rb +18 -0
  36. data/lib/fiona7/attribute_writers/linklist_as_linklist.rb +16 -0
  37. data/lib/fiona7/attribute_writers/multienum_as_multienum.rb +11 -0
  38. data/lib/fiona7/attribute_writers/multienum_as_text.rb +12 -0
  39. data/lib/fiona7/attribute_writers/number_as_string.rb +11 -0
  40. data/lib/fiona7/attribute_writers/reference_as_linklist.rb +17 -0
  41. data/lib/fiona7/attribute_writers/reference_as_string.rb +11 -0
  42. data/lib/fiona7/attribute_writers/referencelist_as_linklist.rb +19 -0
  43. data/lib/fiona7/attribute_writers/referencelist_as_text.rb +13 -0
  44. data/lib/fiona7/attribute_writers/simple.rb +11 -0
  45. data/lib/fiona7/attribute_writers/stringlist_as_text.rb +16 -0
  46. data/lib/fiona7/attribute_writers/widgetlist_as_linklist.rb +33 -0
  47. data/lib/fiona7/builder/obj_builder.rb +12 -201
  48. data/lib/fiona7/controllers/rest_api/obj_controller.rb +23 -5
  49. data/lib/fiona7/engine.rb +39 -0
  50. data/lib/fiona7/json/obj_decorator.rb +29 -121
  51. data/lib/fiona7/prefetch/obj_prefetch.rb +42 -0
  52. data/lib/fiona7/prefetch/widget_resolver_prefetch.rb +36 -0
  53. data/lib/fiona7/scrivito_patches/date_attribute.rb +16 -0
  54. data/lib/fiona7/tools/attribute_remover.rb +70 -0
  55. data/lib/fiona7/type_register.rb +9 -1
  56. data/lib/fiona7/version.rb +1 -1
  57. data/lib/fiona7/widget_resolver.rb +6 -6
  58. metadata +51 -4
@@ -15,6 +15,9 @@ require 'fiona7/assert'
15
15
  require 'fiona7/json/reverse_obj_decorator'
16
16
  require 'fiona7/json/typeless_obj_decorator'
17
17
 
18
+ require 'fiona7/prefetch/obj_prefetch'
19
+ require 'fiona7/prefetch/widget_resolver_prefetch'
20
+
18
21
  module Fiona7
19
22
  module Controllers
20
23
  module RestAPI
@@ -61,10 +64,13 @@ module Fiona7
61
64
  # FIXME: code duplication with fetch_by_id
62
65
  obj_ids = payload.with_indifferent_access[:ids]
63
66
  klass = revision_id.start_with?('f') ? ReleasedObj : EditedObj
64
- objs = klass.where(obj_id: obj_ids)
65
- objs_arr = obj_ids.map {|obj_id| objs.find {|o| o.id == obj_id.to_i } }
66
67
 
67
- decorated = objs_arr.map {|obj| obj ? Fiona7::JSON::ReverseObjDecorator.new(klass, obj) : nil }
68
+ # preloads all objects in one query
69
+ objs_arr = Prefetch::ObjPrefetch.new(klass, obj_ids).find_many(obj_ids)
70
+ # preloads all widgets in one query
71
+ widget_prefetch = Prefetch::WidgetResolverPrefetch.new(klass, objs_arr.compact)
72
+
73
+ decorated = objs_arr.map {|obj| obj ? Fiona7::JSON::ReverseObjDecorator.new(klass, obj, widget_prefetch.widget_resolver(obj)) : nil }
68
74
  response = ::JSON.parse(::ActiveSupport::JSON.encode({"results" => decorated}))
69
75
  return response
70
76
  end
@@ -112,11 +118,23 @@ module Fiona7
112
118
  conti = {}
113
119
  end
114
120
 
121
+ # This simulates the behavior of the backend:
122
+ # In absence of other input backend always sorts by ID
123
+ # or SCORE if an elastic search for such request would
124
+ # produce a score.
125
+ if (params[:query]||[]).any?{|op| (op[:operator]||op["operator"]).to_s =~ /search/ }
126
+ default_sort_order = :desc
127
+ default_sort_by = :_score
128
+ else
129
+ default_sort_order = :asc
130
+ default_sort_by = :id
131
+ end
132
+
115
133
  offset = conti[:offset] || params[:offset] || 0
116
134
  # 10 is the default value in original implementation
117
135
  size = conti[:size] || params[:size] || 10
118
- order = (conti[:sort_order] || params[:sort_order] || :asc).to_sym
119
- sort = (conti[:sort_by] || params[:sort_by] || :id).to_sym
136
+ order = (conti[:sort_order] || params[:sort_order] || default_sort_order).to_sym
137
+ sort = (conti[:sort_by] || params[:sort_by] || default_sort_by).to_sym
120
138
 
121
139
  if params[:query].present?
122
140
  normalize_query_params(params[:query])
data/lib/fiona7/engine.rb CHANGED
@@ -59,6 +59,9 @@ require 'fiona7/type_synchronizer'
59
59
  require 'fiona7/naive_search_engine'
60
60
  require 'fiona7/verity_search_engine'
61
61
 
62
+ # tools
63
+ require 'fiona7/tools/attribute_remover'
64
+
62
65
  module Fiona7
63
66
  mattr_accessor :mode
64
67
  mattr_accessor :login
@@ -71,6 +74,42 @@ module Fiona7
71
74
  :release_obj => []
72
75
  }
73
76
 
77
+ # Allows to specify custom typing for virtual attribute types
78
+ # Besides the built in mapping:
79
+ # link: :linklist,
80
+ # reference: :linklist,
81
+ # referencelist: :linklist,
82
+ # widgetlist: :linklist,
83
+ # binary: :linklist,
84
+ # number: :string,
85
+ # stringlist: :text
86
+ #
87
+ # Following other options are supported:
88
+ # reference: :string,
89
+ # referencelist: :text
90
+ #
91
+ # Custom mappings can be specified per obj_class:
92
+ #
93
+ # custom_attribute_types = {
94
+ # 'MyPage' => {
95
+ # child_order: :text
96
+ # }
97
+ # }
98
+ #
99
+ # or per attribute:
100
+ #
101
+ # custom_attribute_types = {
102
+ # child_order: :text
103
+ # }
104
+ #
105
+ # or using a callable entity like a proc:
106
+ #
107
+ # custom_attribute_types = lambda do |obj_class, attribute|
108
+ # :text if attribute == :child_order
109
+ # end
110
+ mattr_accessor :custom_attribute_types
111
+ self.custom_attribute_types = {}
112
+
74
113
  mattr_accessor :search_engine
75
114
  self.search_engine = Fiona7::VeritySearchEngine
76
115
 
@@ -6,10 +6,11 @@ module Fiona7
6
6
  end
7
7
 
8
8
  require 'fiona7/assert'
9
- require 'fiona7/link_converter/fiona_to_scrivito'
10
9
  require 'fiona7/json/widget_decorator'
11
10
  require 'fiona7/widget_resolver'
12
- require 'fiona7/blob_id_generator'
11
+ require 'fiona7/prefetch/obj_prefetch'
12
+
13
+ require 'fiona7/attribute_readers/factory'
13
14
 
14
15
  module Fiona7
15
16
  module JSON
@@ -18,12 +19,10 @@ module Fiona7
18
19
  @klass = klass
19
20
  @obj = obj
20
21
 
21
- @link_converter = LinkConverter::FionaToScrivito.new(obj)
22
-
23
22
  if widget_resolver
24
23
  @widget_resolver = widget_resolver
25
24
  elsif (widget_links = @obj.attr_values["X_widget_pool"])
26
- @widget_resolver = WidgetResolver.new(widget_links, @klass)
25
+ @widget_resolver = WidgetResolver.new(widget_links, Prefetch::ObjPrefetch.new(klass))
27
26
  end
28
27
  end
29
28
 
@@ -58,137 +57,46 @@ module Fiona7
58
57
 
59
58
  def add_custom_attributes(attrs, *args)
60
59
  type_definition = Fiona7::TypeRegister.instance.read_mangled(@obj.obj_class)
60
+ factory = Fiona7::AttributeReaders::Factory.new(@obj, type_definition, @widget_resolver)
61
+
61
62
  type_definition.attrs.each do |attribute|
62
63
  virtual_attribute_name = attribute.name
63
64
  real_attribute_name = attribute.real_name
64
65
  virtual_attribute_type = attribute.type.to_sym
65
-
66
- val = case virtual_attribute_type
67
- when :link
68
- deserialize_link(@obj.attr_values[real_attribute_name].try(:first))
69
- when :linklist
70
- (@obj.attr_values[real_attribute_name] || []).map {|link| deserialize_link(link) }
71
- when :multienum
72
- @obj[real_attribute_name] || []
73
- when :reference
74
- deserialize_reference(@obj.attr_values[real_attribute_name].try(:first))
75
- when :referencelist
76
- (@obj.attr_values[real_attribute_name] || []).map {|link| deserialize_reference(link) }.compact
77
- when :date
78
- if real_attribute_name == "valid_from" || real_attribute_name == "valid_until"
79
- # a bug in fiona connector -> [valid_(from|until)] returns a raw string
80
- @obj[real_attribute_name]
81
- else
82
- @obj[real_attribute_name].try(:utc).try(:to_iso)
83
- end
84
- when :widgetlist
85
- deserialize_widget_field(@obj.attr_values[real_attribute_name] || [])
86
- when :html
87
- deserialize_html(@obj[real_attribute_name])
88
- when :markdown
89
- nil
90
- when :binary
91
- if !Fiona7.mode == :legacy || !@obj.binary?
92
- deserialize_binary(@obj.attr_values[real_attribute_name].try(:first))
93
- else
94
- {id: Fiona7::BlobIdGenerator.new(@obj.id, @obj.last_changed).call}
95
- end
96
- when :enum, :multienum, :text, :string
97
- if real_attribute_name == "suppress_export"
98
- @obj[real_attribute_name].to_s
99
- else
100
- @obj[real_attribute_name]
101
- end
102
- when :number
103
- @obj[real_attribute_name].to_f
104
- when :stringlist
105
- if Fiona7.mode == :legacy && real_attribute_name == "channels"
106
- @obj["channels"] || []
107
- else
108
- # TODO: get rid of this rescue nil
109
- ::JSON.parse(@obj[real_attribute_name]) rescue nil
110
- end
66
+ # This is probably the most important line in this whole class.
67
+ # An intuitive way of reading the real attribute type
68
+ # would be to just look-up in the attribute definition.
69
+ #
70
+ # But this approach is wrong. Consider the following case:
71
+ # An attribute :child_order is created with real type :linklist
72
+ # This proves problematic (because of the unwanted links in CMS)
73
+ # and is later changed to :text. But the CMS still contains
74
+ # "old" objects with :child_order modelled as :linklist.
75
+ # Therefore we must lookup the correct typing in the attribute itself.
76
+ #
77
+ # It is also possible that the obj does not have any content for the
78
+ # attribute. In this case we just use the type provided by
79
+ # the type definition.
80
+ real_attribute_type = ((@obj.attr_dict.send(:attr_defs) || {})[real_attribute_name.to_s]||{})["type"] ||
81
+ attribute.real_type
82
+
83
+ worker = factory.call(real_attribute_name, virtual_attribute_type, real_attribute_type)
84
+ if worker.nil?
85
+ Rails.logger.error("Unable to deserialize #{virtual_attribute_name} in #{type_definition.name}")
86
+ val = nil
111
87
  else
112
- Assert.success(
113
- false,
114
- "Unknown attribute type #{virtual_attribute_type} for attribute: #{virtual_attribute_name}"
115
- )
88
+ val = worker.call
116
89
  end
117
90
 
118
91
  # TODO: better handling for attribute types
119
92
  if virtual_attribute_type == :text
120
93
  virtual_attribute_type = :string
121
94
  end
122
- attrs[virtual_attribute_name] = [val, virtual_attribute_type]
123
- end
124
- end
125
95
 
126
- def deserialize_link(link)
127
- if link
128
- deserialized = {
129
- # remove possible external prefix for protcol-less urls
130
- url: link["url"].try(:gsub, /\Aexternal:/, ''),
131
- title: link["title"],
132
- target: link["target"],
133
- # content service uses destination
134
- destination: link["destination"].to_s,
135
- # rest api uses obj_id
136
- obj_id: link["destination"].to_s,
137
- query: link["search"],
138
- fragment: link["fragment"]
139
- }
140
-
141
- # TODO: refactor this code
142
- if deserialized[:url].present?
143
- deserialized.delete(:destination)
144
- deserialized.delete(:obj_id)
145
- end
146
-
147
- deserialized
148
- end
149
- end
150
-
151
- def deserialize_reference(link)
152
- if link && link["type"] == "internal"
153
- link["destination"].to_s
154
- end
155
- end
156
-
157
- def deserialize_widget_field(linklist)
158
- if !linklist.empty?
159
- widget_ids = []
160
- widgets = linklist.map do |link|
161
- widget_id = link["title"]
162
- # sometimes a released page references an unreleased widget
163
- # it is then impossible to load it into widget_pool
164
- # but the reference in the widgetlist field is still there
165
- # therefore we need to check if the widget has been loaded
166
- # into the pool - only then it can be included
167
- if valid_widget_id?(widget_id)
168
- widget_ids << widget_id
169
- end
170
- end
171
-
172
- widget_ids
173
- end
174
- end
175
-
176
- def deserialize_html(html)
177
- @link_converter.convert(html)
178
- end
179
-
180
- def deserialize_binary(link)
181
- if link
182
- binary_id = link["title"]
183
- if binary_id.length == 32 && binary_id =~ /0{16,}0*[0-9]+/
184
- {id: binary_id}
185
- end
96
+ attrs[virtual_attribute_name] = [val, virtual_attribute_type]
186
97
  end
187
98
  end
188
99
 
189
- def valid_widget_id?(widget_id)
190
- @widget_resolver && @widget_resolver.path_map[widget_id]
191
- end
192
100
 
193
101
  def replace_widget_pool(attrs, *args)
194
102
  if (widget_links = @obj.attr_values["X_widget_pool"]) && @widget_resolver
@@ -0,0 +1,42 @@
1
+ require 'set'
2
+
3
+ module Fiona7
4
+ module Prefetch
5
+ class ObjPrefetch
6
+ def initialize(klass, prefetch_ids=[])
7
+ self.klass = klass
8
+ self.prefetched_ids = Set.new
9
+ self.object_id_map = {}
10
+
11
+ self.prefetch_objects(prefetch_ids)
12
+ end
13
+
14
+ # Returns an object with given ID or nil if no matching object found
15
+ def find_one(id)
16
+ self.find_many([id]).first
17
+ end
18
+
19
+ # Returns an array with objects for given IDs.
20
+ # The array is ordered according to the IDs given.
21
+ # For missing objects the array is filled with nil values
22
+ def find_many(ids)
23
+ self.prefetch_objects(ids)
24
+ ids.map { |id| self.object_id_map[id.to_i] }
25
+ end
26
+
27
+ protected
28
+ attr_accessor :klass, :prefetched_ids, :object_id_map
29
+
30
+ def prefetch_objects(prefetch_ids)
31
+ missing = prefetch_ids.select {|id| !self.prefetched_ids.include?(id) }
32
+
33
+ return if missing.empty?
34
+
35
+ self.klass.where(obj_id: missing).each do |obj|
36
+ self.prefetched_ids << obj.id
37
+ self.object_id_map[obj.id] = obj
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,36 @@
1
+ require 'fiona7/prefetch/obj_prefetch'
2
+ require 'fiona7/widget_resolver'
3
+
4
+ module Fiona7
5
+ module Prefetch
6
+ class WidgetResolverPrefetch
7
+ def initialize(klass, objects=[])
8
+ widget_object_ids = self.collect_widget_object_ids(objects)
9
+
10
+ self.obj_prefetch = ObjPrefetch.new(klass, widget_object_ids)
11
+ end
12
+
13
+ # Returns a widget resolver for given object.
14
+ # If the object is already known to the prefetcher,
15
+ # then widgets will be loaded from memory,
16
+ # instead of database.
17
+ def widget_resolver(object)
18
+ WidgetResolver.new(
19
+ object.attr_values["X_widget_pool"]||[],
20
+ self.obj_prefetch
21
+ )
22
+ end
23
+
24
+ protected
25
+ attr_accessor :obj_prefetch
26
+
27
+ def collect_widget_object_ids(objects)
28
+ objects.map do |object|
29
+ (object.attr_values["X_widget_pool"]||[]).map do |widget_hash|
30
+ widget_hash["destination"].try(:to_i)
31
+ end
32
+ end.flatten.compact
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,16 @@
1
+ require 'scrivito/date_attribute'
2
+
3
+ module Scrivito
4
+ module DateAttribute
5
+ def self.deserialize_from_backend(iso_date_time)
6
+ return nil unless iso_date_time
7
+ #return nil if iso_date.to_s.blank?
8
+
9
+ if iso_date_time.to_s =~ /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/
10
+ Time.utc($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i).in_time_zone
11
+ else
12
+ raise "The value is not a valid ISO date time: #{iso_date_time.inspect}"
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,70 @@
1
+ require 'fiona7/obj_class_name_mangler'
2
+ require 'fiona7/attribute_name_mangler'
3
+
4
+ module Fiona7
5
+ module Tools
6
+ # This class is used to remove attributes from obj classes.
7
+ # Normally it is not required, because the user can just
8
+ # remove an attribute from the model definition and the
9
+ # attribute becomes invisible.
10
+ # The attribute still persists, but becomes inaccessible.
11
+ #
12
+ # In some cases it may be required to garbage collect
13
+ # the removed attribute, for example to change the type
14
+ # of an attribute, the attribute has to be removed first.
15
+ #
16
+ # This class behaves differently for different configrations.
17
+ # In standalone mode the attribute is removed from
18
+ # the specified obj class and also from CMS.
19
+ # In legacy mode the attribute is removed from
20
+ # the specified obj class. If there are no more obj classes
21
+ # referencing this attribute, it also gets deleted from CMS.
22
+ class AttributeRemover
23
+ def initialize(obj_class, attr_name)
24
+ self.obj_class = ObjClassNameMangler.new(obj_class).mangle
25
+ self.attr_name = AttributeNameMangler.new(attr_name, obj_class).mangle
26
+
27
+ self.original_attr_name = attr_name
28
+ self.original_obj_class = obj_class
29
+ end
30
+
31
+ def remove
32
+ if rc_attr = RailsConnector::Attribute.find_by_attribute_name(self.attr_name)
33
+ # fetch the names of all obj classes referencing this attribute
34
+ obj_classes = rc_attr.obj_class_defs.map(&:name)
35
+ if obj_classes.include?(self.obj_class)
36
+ cm_obj_class = Reactor::Cm::ObjClass.get(self.obj_class)
37
+ # this removes the attribute from the obj class
38
+ cm_obj_class.attributes = (cm_obj_class.attributes - [self.attr_name])
39
+ # flush reactor cache
40
+ RailsConnector::Meta::EagerLoader.instance.forget_obj_class(self.obj_class)
41
+ # flush type register caches
42
+ Fiona7::TypeRegister.instance.send(:load, self.original_obj_class)
43
+ if typedef = Fiona7::TypeRegister.instance.usr_defs[self.original_obj_class]
44
+ # There is a very subtle bug hidden here:
45
+ # If you have an enum or multienum attribute, remove it and
46
+ # trigger synchronize (via write request) it will be created
47
+ # as string or text.
48
+ #
49
+ # Easiest way to prevent this bug is to restart the app
50
+ # between the requests.
51
+ typedef.remove_attribute(self.original_attr_name)
52
+ end
53
+ # one reference less!
54
+ obj_classes.delete(self.obj_class)
55
+ end
56
+
57
+ # NOTE: this will always be true in standlone mode
58
+ if obj_classes.empty?
59
+ # attribute can be garbage collected
60
+ cm_attr = Reactor::Cm::Attribute.get(self.attr_name)
61
+ cm_attr.delete!
62
+ end
63
+ end
64
+ end
65
+
66
+ protected
67
+ attr_accessor :obj_class, :attr_name, :original_obj_class, :original_attr_name
68
+ end
69
+ end
70
+ end