europeana-blacklight 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,51 @@
1
+ module Europeana
2
+ module Blacklight
3
+ class Document
4
+ ##
5
+ # Methods for obtaining "more like this" documents
6
+ module MoreLikeThis
7
+ # @param [String] param Name of API parameter to restrict query to
8
+ # @return [String]
9
+ def more_like_this_query(param = nil)
10
+ queries = more_like_this_field_queries(param)
11
+ return nil unless queries.size > 0
12
+ field_queries = queries.join(' OR ')
13
+ "(#{field_queries}) NOT europeana_id:\"#{self.id}\""
14
+ end
15
+
16
+ protected
17
+
18
+ def more_like_this_logic
19
+ [
20
+ { param: 'title', fields: 'title', boost: 0.3 },
21
+ { param: 'who', fields: 'proxies.dcCreator', boost: 0.5 },
22
+ { param: 'DATA_PROVIDER', fields: 'aggregations.edmDataProvider', boost: 0.2 },
23
+ { param: 'what', fields: ['proxies.dcType', 'proxies.dcSubject', 'concepts.about'], boost: 0.8 },
24
+ ]
25
+ end
26
+
27
+ def more_like_this_field_terms(*fields)
28
+ fields.flatten.map do |field|
29
+ fetch(field, []).compact
30
+ end.flatten
31
+ end
32
+
33
+ def more_like_this_field_queries(param = nil)
34
+ logic = more_like_this_logic.select do |component|
35
+ param.nil? || component[:param] == param
36
+ end
37
+ logic.map do |component|
38
+ field_terms = more_like_this_field_terms(component[:fields])
39
+ more_like_this_param_query(component[:param], field_terms, component[:boost])
40
+ end.compact
41
+ end
42
+
43
+ def more_like_this_param_query(param, terms, boost)
44
+ return nil unless terms.present?
45
+ or_terms = terms.map { |v| '"' + v + '"' }.join(' OR ')
46
+ "#{param}: (#{or_terms})^#{boost}"
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,88 @@
1
+ require 'active_support/core_ext/hash'
2
+
3
+ module Europeana
4
+ module Blacklight
5
+ class Document
6
+ ##
7
+ # Methods for working with relations between objects in Europeana documents
8
+ module Relations
9
+ attr_reader :relations
10
+
11
+ def fetch_through_relation(key, *default)
12
+ field = nested_field_key(key)
13
+ container = nested_field_container(key)
14
+ value = [container].flatten.compact.collect do |target|
15
+ target.fetch(field, *default)
16
+ end.compact.flatten
17
+ end
18
+
19
+ def extract_relations(source_doc)
20
+ fields = source_doc.except(relation_keys)
21
+
22
+ relations = HashWithIndifferentAccess.new
23
+
24
+ relation_keys.each do |k|
25
+ if source_doc.key?(k)
26
+ if source_doc[k].is_a?(Hash)
27
+ relations[k] = self.class.new(source_doc[k], nil)
28
+ elsif source_doc[k].is_a?(Array)
29
+ relations[k] = source_doc[k].map { |v| self.class.new(v, nil) }
30
+ else
31
+ fail StandardError,
32
+ 'Relations should be a collection of objects.'
33
+ end
34
+ end
35
+ end
36
+
37
+ [fields, relations]
38
+ end
39
+
40
+ def field_in_relation?(field)
41
+ keys = split_edm_key(field)
42
+ (keys.size > 1) && relations.key?(keys.first)
43
+ end
44
+
45
+ def nested_field_key(field)
46
+ split_edm_key(field).last
47
+ end
48
+
49
+ def nested_field_container(field)
50
+ container = self
51
+ if field_in_relation?(field)
52
+ keys = split_edm_key(field)
53
+ field = keys.last
54
+ keys[0..-2].each do |relation_key|
55
+ container = [container].flatten.collect { |d| d.send(relation_key.to_sym) }
56
+ end
57
+ end
58
+ container
59
+ end
60
+
61
+ def method_missing(m, *args, &b)
62
+ if has_relation?(m)
63
+ relations[m.to_s]
64
+ elsif relation_keys.include?(m)
65
+ []
66
+ else
67
+ super
68
+ end
69
+ end
70
+
71
+ def has_relation?(name)
72
+ relations.key?(name.to_s)
73
+ end
74
+
75
+ protected
76
+
77
+ def split_edm_key(key)
78
+ key.to_s.split('.')
79
+ end
80
+
81
+ def relation_keys
82
+ [:agents, :aggregations, :concepts, :europeanaAggregation, :places,
83
+ :providedCHOs, :proxies, :timespans, :webResources]
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,36 @@
1
+ module Europeana
2
+ module Blacklight
3
+ ##
4
+ # Blacklight document presenter for Europeana documents
5
+ class DocumentPresenter < ::Blacklight::DocumentPresenter
6
+ include ActionView::Helpers::AssetTagHelper
7
+
8
+ def render_document_show_field_value(field, options = {})
9
+ render_nested_field_value(field, :show, options)
10
+ end
11
+
12
+ def render_index_field_value(field, options = {})
13
+ render_nested_field_value(field, :index, options)
14
+ end
15
+
16
+ def render_nested_field_value(field, context, options = {})
17
+ key = @document.nested_field_key(field)
18
+ container = @document.nested_field_container(field)
19
+
20
+ field_config = @configuration.send(:"#{context}_fields")[key]
21
+ value = options[:value] || begin
22
+ [container].flatten.compact.collect do |target|
23
+ presenter = self.class.new(target, @controller, @configuration)
24
+ presenter.get_field_values(key, field_config, options)
25
+ end.compact.flatten
26
+ end
27
+
28
+ render_field_value(value, field_config)
29
+ end
30
+
31
+ def get_field_values(field, field_config, options = {})
32
+ Document.localize_lang_map(super)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,8 @@
1
+ module Europeana
2
+ module Blacklight
3
+ ##
4
+ # Facet paginator for Europeana API
5
+ class FacetPaginator < Blacklight::FacetPaginator
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,91 @@
1
+ module Europeana
2
+ module Blacklight
3
+ ##
4
+ # Europeana API response for BL
5
+ class Response < HashWithIndifferentAccess
6
+ require 'europeana/blacklight/response/pagination'
7
+ require 'europeana/blacklight/response/facets'
8
+ require 'europeana/blacklight/response/more_like_this'
9
+
10
+ include Pagination
11
+ include Facets
12
+ include MoreLikeThis
13
+
14
+ attr_reader :request_params
15
+ attr_accessor :document_model, :blacklight_config
16
+
17
+ def initialize(data, request_params, options = {})
18
+ super(force_to_utf8(data))
19
+ @request_params = request_params
20
+ self.document_model = options[:document_model] || Document
21
+ self.blacklight_config = options[:blacklight_config]
22
+ end
23
+
24
+ def update(other_hash)
25
+ other_hash.each_pair { |key, value| self[key] = value }
26
+ self
27
+ end
28
+
29
+ def params
30
+ self['params'] ? self['params'] : request_params
31
+ end
32
+
33
+ def rows
34
+ params[:rows].to_i
35
+ end
36
+
37
+ def sort
38
+ params[:sort]
39
+ end
40
+
41
+ def documents
42
+ @documents ||= (self.key?('object') ? [self['object']] : (self['items'] || [])).collect do |doc|
43
+ document_model.new(doc, self)
44
+ end
45
+ end
46
+ alias_method :docs, :documents
47
+
48
+ def grouped
49
+ []
50
+ end
51
+
52
+ def group(_key)
53
+ nil
54
+ end
55
+
56
+ def grouped?
57
+ false
58
+ end
59
+
60
+ def export_formats
61
+ documents.map { |x| x.export_formats.keys }.flatten.uniq
62
+ end
63
+
64
+ def total
65
+ self[:totalResults].to_s.to_i
66
+ end
67
+
68
+ def start
69
+ params[:start].to_s.to_i - 1
70
+ end
71
+
72
+ def empty?
73
+ total == 0
74
+ end
75
+
76
+ private
77
+
78
+ def force_to_utf8(value)
79
+ case value
80
+ when Hash
81
+ value.each { |k, v| value[k] = force_to_utf8(v) }
82
+ when Array
83
+ value.each { |v| force_to_utf8(v) }
84
+ when String
85
+ value.force_encoding('utf-8') if value.respond_to?(:force_encoding)
86
+ end
87
+ value
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,145 @@
1
+ module Europeana
2
+ module Blacklight
3
+ class Response
4
+ ##
5
+ # Facets for {Europeana::Blacklight::Response}
6
+ #
7
+ # Based on {Blacklight::SolrResponse::Facets} v5.10.2
8
+ module Facets
9
+ require 'ostruct'
10
+
11
+ # represents a facet value; which is a field value and its hit count
12
+ class FacetItem < OpenStruct
13
+ def initialize(*args)
14
+ options = args.extract_options!
15
+
16
+ # Backwards-compat method signature
17
+ value = args.shift
18
+ hits = args.shift
19
+
20
+ options[:value] = value if value
21
+ options[:hits] = hits if hits
22
+
23
+ super(options)
24
+ end
25
+
26
+ def label
27
+ super || value
28
+ end
29
+
30
+ def as_json(props = nil)
31
+ table.as_json(props)
32
+ end
33
+ end
34
+
35
+ # represents a facet; which is a field and its values
36
+ class FacetField
37
+ attr_reader :name, :items
38
+
39
+ def initialize(name, items, options = {})
40
+ @name, @items = name, items
41
+ @options = options
42
+ end
43
+
44
+ def limit
45
+ @options[:limit] || default_limit
46
+ end
47
+
48
+ def offset
49
+ @options[:offset] || default_offset
50
+ end
51
+
52
+ def sort
53
+ # Europeana API does not support facet sorting
54
+ nil
55
+ end
56
+
57
+ private
58
+
59
+ # @see http://labs.europeana.eu/api/search/#offset-and-limit-of-facets
60
+ def default_limit
61
+ 100
62
+ end
63
+
64
+ # @see http://labs.europeana.eu/api/search/#offset-and-limit-of-facets
65
+ def default_offset
66
+ 0
67
+ end
68
+ end
69
+
70
+ def aggregations
71
+ @aggregations ||= {}.merge(facet_field_aggregations).merge(facet_query_aggregations)
72
+ end
73
+
74
+ def facet_fields
75
+ @facet_fields ||= self['facets'] || []
76
+ end
77
+
78
+ def facet_queries
79
+ @facet_queries ||= self['facet_queries'] || {}
80
+ end
81
+
82
+ private
83
+
84
+ ##
85
+ # Convert API's facets response into a hash of
86
+ # {Europeana::Blacklight::Response::Facet::FacetField} objects
87
+ def facet_field_aggregations
88
+ facet_fields.each_with_object({}) do |facet, hash|
89
+ facet_field_name = facet['name']
90
+
91
+ items = facet['fields'].collect do |value|
92
+ FacetItem.new(value: value['label'], hits: value['count'])
93
+ end
94
+
95
+ hash[facet_field_name] = FacetField.new(facet_field_name, items, facet_field_aggregation_options(facet_field_name))
96
+
97
+ if blacklight_config && !blacklight_config.facet_fields[facet_field_name]
98
+ # alias all the possible blacklight config names..
99
+ blacklight_config.facet_fields.select { |_k, v| v.field == facet_field_name }.each do |key, _|
100
+ hash[key] = hash[facet_field_name]
101
+ end
102
+ end
103
+ end
104
+ end
105
+
106
+ def facet_field_aggregation_options(name)
107
+ options = {}
108
+
109
+ if params[:"f.#{name}.facet.limit"]
110
+ options[:limit] = params[:"f.#{name}.facet.limit"].to_i
111
+ elsif params[:'facet.limit']
112
+ options[:limit] = params[:'facet.limit'].to_i
113
+ end
114
+
115
+ if params[:"f.#{name}.facet.offset"]
116
+ options[:offset] = params[:"f.#{name}.facet.offset"].to_i
117
+ elsif params[:'facet.offset']
118
+ options[:offset] = params[:'facet.offset'].to_i
119
+ end
120
+
121
+ options
122
+ end
123
+
124
+ ##
125
+ # Aggregate API's facet_query response into the virtual facet fields
126
+ # defined in the blacklight configuration
127
+ def facet_query_aggregations
128
+ return {} unless blacklight_config
129
+
130
+ blacklight_config.facet_fields.select { |_k, v| v.query }.each_with_object({}) do |(field_name, facet_field), hash|
131
+ salient_facet_queries = facet_field.query.map { |_k, x| x[:fq] }
132
+ items = []
133
+ facet_queries.select { |k, _v| salient_facet_queries.include?(k) }.reject { |_value, hits| hits == 0 }.map do |value, hits|
134
+ salient_fields = facet_field.query.select { |_k, v| v[:fq] == value }
135
+ key = ((salient_fields.keys if salient_fields.respond_to? :keys) || salient_fields.first).first
136
+ items << FacetItem.new(value: key, hits: hits, label: facet_field.query[key][:label])
137
+ end
138
+
139
+ hash[field_name] = FacetField.new(field_name, items)
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,19 @@
1
+ module Europeana
2
+ module Blacklight
3
+ class Response
4
+ ##
5
+ # MLT for{Europeana::Blacklight::Response}
6
+ #
7
+ # This will just return blank objects from the MLT methods BL expects.
8
+ module MoreLikeThis
9
+ def more_like(_document)
10
+ []
11
+ end
12
+
13
+ def more_like_this
14
+ {}
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end