repertoire-faceting 0.5.5 → 0.6.0
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.
- checksums.yaml +4 -4
- data/FAQ +23 -17
- data/INSTALL +52 -84
- data/LICENSE +1 -1
- data/README +213 -34
- data/TODO +20 -7
- data/ext/Makefile +24 -14
- data/ext/README.faceting +51 -0
- data/ext/bytea/bytea.sql +173 -0
- data/ext/bytea/faceting_bytea.control +6 -0
- data/ext/common/util.sql +35 -0
- data/ext/faceting--0.6.0.sql +251 -0
- data/ext/faceting_bytea--0.6.0.sql +207 -0
- data/ext/faceting_varbit--0.6.0.sql +198 -0
- data/ext/signature/faceting.control +6 -0
- data/ext/signature/signature.c +740 -0
- data/ext/{signature.o → signature/signature.o} +0 -0
- data/ext/{signature.so → signature/signature.so} +0 -0
- data/ext/signature/signature.sql +217 -0
- data/ext/varbit/faceting_varbit.control +7 -0
- data/ext/varbit/varbit.sql +164 -0
- data/{public → lib/assets}/images/repertoire-faceting/proportional_symbol.png +0 -0
- data/{public → lib/assets}/images/repertoire-faceting/spinner_sm.gif +0 -0
- data/{public → lib/assets}/javascripts/rep.faceting/context.js +2 -2
- data/{public → lib/assets}/javascripts/rep.faceting/ext/earth_facet.js +2 -4
- data/{public → lib/assets}/javascripts/rep.faceting/facet.js +1 -1
- data/{public → lib/assets}/javascripts/rep.faceting/facet_widget.js +3 -8
- data/{public → lib/assets}/javascripts/rep.faceting/nested_facet.js +1 -1
- data/{public → lib/assets}/javascripts/rep.faceting/results.js +1 -1
- data/{public → lib/assets}/javascripts/rep.faceting.js +5 -1
- data/{public → lib/assets}/javascripts/rep.protovis-facets.js +3 -3
- data/lib/assets/javascripts/rep.widgets/events.js +51 -0
- data/lib/assets/javascripts/rep.widgets/global.js +50 -0
- data/lib/assets/javascripts/rep.widgets/model.js +159 -0
- data/lib/assets/javascripts/rep.widgets/widget.js +213 -0
- data/lib/assets/javascripts/rep.widgets.js +14 -0
- data/{public → lib/assets}/stylesheets/rep.faceting.css +1 -1
- data/lib/repertoire-faceting/adapters/postgresql_adapter.rb +107 -48
- data/lib/repertoire-faceting/facets/abstract_facet.rb +43 -27
- data/lib/repertoire-faceting/facets/basic_facet.rb +23 -22
- data/lib/repertoire-faceting/facets/nested_facet.rb +50 -27
- data/lib/repertoire-faceting/model.rb +101 -65
- data/lib/repertoire-faceting/rails/engine.rb +8 -0
- data/lib/repertoire-faceting/rails/postgresql_adapter.rb +0 -1
- data/lib/repertoire-faceting/rails/relation.rb +0 -1
- data/lib/repertoire-faceting/railtie.rb +0 -1
- data/lib/repertoire-faceting/relation/calculations.rb +7 -2
- data/lib/repertoire-faceting/relation/query_methods.rb +17 -4
- data/lib/repertoire-faceting/routing.rb +2 -5
- data/lib/repertoire-faceting/tasks/all.rake +5 -4
- data/lib/repertoire-faceting/tasks/client.rake +2 -5
- data/lib/repertoire-faceting/version.rb +1 -1
- data/lib/repertoire-faceting.rb +2 -4
- data/{public → vendor/assets}/javascripts/google-earth-extensions.js +0 -0
- data/{public → vendor/assets}/javascripts/protovis.js +0 -0
- metadata +78 -78
- data/ext/README.signature +0 -33
- data/ext/signature.c +0 -740
- data/ext/signature.sql +0 -342
- data/ext/signature.sql.IN +0 -342
- data/ext/uninstall_signature.sql +0 -4
- data/ext/uninstall_signature.sql.IN +0 -4
- data/lib/repertoire-faceting/adapters/abstract_adapter.rb +0 -18
- data/lib/repertoire-faceting/relation/spawn_methods.rb +0 -26
@@ -0,0 +1,159 @@
|
|
1
|
+
/*
|
2
|
+
* Repertoire abstract ajax model, for use with widget framework
|
3
|
+
*
|
4
|
+
* Copyright (c) 2009 MIT Hyperstudio
|
5
|
+
* Christopher York, 11/2009
|
6
|
+
*
|
7
|
+
* Requires jquery 1.3.2+
|
8
|
+
* Support: Firefox 3+ & Safari 4+. IE emphatically not supported.
|
9
|
+
*/
|
10
|
+
|
11
|
+
//
|
12
|
+
// Abstract model for ajax widgets. This class includes convenience methods for listening to
|
13
|
+
// events on models, for jquery ajax calls, and for param encoding in Merb's style.
|
14
|
+
//
|
15
|
+
// Handles:
|
16
|
+
// - change publication and observing
|
17
|
+
// - url/query-string construction
|
18
|
+
//
|
19
|
+
// Usage. The model provides a facade to your ajax webservice, and may hold state for a widget.
|
20
|
+
// In some cases (e.g. working with large server-side datasets), it may be appropriate to
|
21
|
+
// cache data in the model and fetch it as needed from the webservice.
|
22
|
+
//
|
23
|
+
// Using the ajax param encoders. These convenience methods help you construct the url and
|
24
|
+
// assemble params for an ajax call. It's not required to use them, although they make life
|
25
|
+
// easier.
|
26
|
+
//
|
27
|
+
// self.items = function(callback) {
|
28
|
+
// var url = self.default_url(['projects', 'results']);
|
29
|
+
// self.fetch(params, url, 'html', callback);
|
30
|
+
// }
|
31
|
+
//
|
32
|
+
repertoire.model = function(options) {
|
33
|
+
// this object is an abstract class
|
34
|
+
var self = {};
|
35
|
+
|
36
|
+
// default: no options specified
|
37
|
+
options = options || {};
|
38
|
+
|
39
|
+
// mixin event handler functionality
|
40
|
+
repertoire.events(self);
|
41
|
+
|
42
|
+
//
|
43
|
+
// Update the data model given the current state
|
44
|
+
//
|
45
|
+
// You may either pre-process the state and write your own webservice access methods
|
46
|
+
// or use self.fetch for a generic webservice, e.g.
|
47
|
+
//
|
48
|
+
// self.update = function(state, callback) {
|
49
|
+
// var url = self.default_url(['projects', 'results']);
|
50
|
+
// self.fetch(state, url, 'html', callback);
|
51
|
+
// }
|
52
|
+
//
|
53
|
+
self.update = function(state, callback) {
|
54
|
+
throw "Abstract function: redefine self.update() in your data model."
|
55
|
+
};
|
56
|
+
|
57
|
+
//
|
58
|
+
// Utility method to issue an ajax HTTP GET to fetch data from a webservice
|
59
|
+
//
|
60
|
+
// params: params to send as http query line
|
61
|
+
// url: url of webservice to access
|
62
|
+
// type: type of data returned (e.g. 'json', 'html')
|
63
|
+
// callback: function to call with returned data
|
64
|
+
//
|
65
|
+
self.fetch = function(params, url, type, callback, $elem, async) {
|
66
|
+
if (async == null)
|
67
|
+
async = true;
|
68
|
+
|
69
|
+
var spinnerClass = options.spinner || 'loading';
|
70
|
+
if ($elem)
|
71
|
+
$elem.addClass(spinnerClass);
|
72
|
+
$.ajax({
|
73
|
+
async: async,
|
74
|
+
url: url,
|
75
|
+
data: self.to_query_string(params),
|
76
|
+
type: 'GET',
|
77
|
+
dataType: type,
|
78
|
+
// content negotiation problems may require:
|
79
|
+
/* beforeSend: function(xhr) { xhr.setRequestHeader("Accept", "application/json") } */
|
80
|
+
success: callback,
|
81
|
+
error: function(request, textStatus, errorThrown) {
|
82
|
+
if ($elem)
|
83
|
+
$elem.html(options.error || 'Could not load');
|
84
|
+
},
|
85
|
+
complete: function () {
|
86
|
+
if ($elem)
|
87
|
+
$elem.removeClass(spinnerClass);
|
88
|
+
}
|
89
|
+
});
|
90
|
+
};
|
91
|
+
|
92
|
+
//
|
93
|
+
// Format a webservice url, preferring options.url if possible
|
94
|
+
//
|
95
|
+
self.default_url = function(default_parts, ext) {
|
96
|
+
var path_prefix = options.path_prefix || '';
|
97
|
+
var parts = default_parts.slice();
|
98
|
+
|
99
|
+
parts.unshift(path_prefix);
|
100
|
+
url = options.url || parts.join('/')
|
101
|
+
|
102
|
+
if (ext)
|
103
|
+
url += '.' + ext;
|
104
|
+
|
105
|
+
return url;
|
106
|
+
}
|
107
|
+
|
108
|
+
//
|
109
|
+
// Convert a structure of params to a URL query string suitable for use in an HTTP GET request, compliant with Merb's format.
|
110
|
+
//
|
111
|
+
// An example:
|
112
|
+
//
|
113
|
+
// Merb::Parse.params_to_query_string(:filter => {:year => [1593, 1597], :genre => ['Tragedy', 'Comedy'] }, :search => 'William')
|
114
|
+
// ==> "filter[genre][]=Tragedy&filter[genre][]=Comedy&filter[year][]=1593&filter[year][]=1597&search=William"
|
115
|
+
//
|
116
|
+
self.to_query_string = function(value, prefix) {
|
117
|
+
var vs = [];
|
118
|
+
prefix = prefix || '';
|
119
|
+
if (value instanceof Array) {
|
120
|
+
jQuery.each(value, function(i, v) {
|
121
|
+
vs.push(self.to_query_string(v, prefix + '[]'));
|
122
|
+
});
|
123
|
+
return vs.join('&');
|
124
|
+
} else if (typeof(value) == "object") {
|
125
|
+
jQuery.each(value, function(k, v) {
|
126
|
+
vs.push(self.to_query_string(v, (prefix.length > 0) ? (prefix + '[' + encodeURIComponent(k) + ']') : encodeURIComponent(k)));
|
127
|
+
});
|
128
|
+
// minor addition to merb: discard empty value lists { e.g. discipline: [] }
|
129
|
+
vs = array_filter(vs, function(x) { return x !== ""; });
|
130
|
+
return vs.join('&');
|
131
|
+
} else {
|
132
|
+
return prefix + '=' + encodeURIComponent(value);
|
133
|
+
}
|
134
|
+
};
|
135
|
+
|
136
|
+
// Apparently IE doesn't support the filter function? -DD via Brett
|
137
|
+
var array_filter = function (thisArray, fun) {
|
138
|
+
var len = thisArray.length;
|
139
|
+
if (typeof fun != "function")
|
140
|
+
throw new TypeError();
|
141
|
+
|
142
|
+
var res = new Array();
|
143
|
+
var thisp = arguments[1];
|
144
|
+
|
145
|
+
for (var i = 0; i < len; i++) {
|
146
|
+
if (i in thisArray) {
|
147
|
+
var val = thisArray[i]; // in case fun mutates this
|
148
|
+
if (fun.call(thisp, val, i, thisArray))
|
149
|
+
res.push(val);
|
150
|
+
}
|
151
|
+
}
|
152
|
+
|
153
|
+
return res;
|
154
|
+
};
|
155
|
+
|
156
|
+
|
157
|
+
// end of model factory function
|
158
|
+
return self;
|
159
|
+
}
|
@@ -0,0 +1,213 @@
|
|
1
|
+
/*
|
2
|
+
* Repertoire abstract ajax widget
|
3
|
+
*
|
4
|
+
* Copyright (c) 2009 MIT Hyperstudio
|
5
|
+
* Christopher York, 09/2009
|
6
|
+
*
|
7
|
+
* Requires jquery 1.3.2+
|
8
|
+
* Support: Firefox 3+ & Safari 4+. IE emphatically not supported.
|
9
|
+
*/
|
10
|
+
|
11
|
+
//
|
12
|
+
// Abstract class for ajax widgets
|
13
|
+
//
|
14
|
+
// Handles:
|
15
|
+
// - url/query-string construction
|
16
|
+
// - data assembly for sending to webservice
|
17
|
+
// - ui event delegation hooks
|
18
|
+
// - hooks for injecting custom behaviour
|
19
|
+
//
|
20
|
+
// Options on all subclassed widgets:
|
21
|
+
//
|
22
|
+
// url - provide a url to over-ride the widget's default
|
23
|
+
// spinner - css class to add to widget during ajax loads
|
24
|
+
// error - text to display if ajax load fails
|
25
|
+
// injectors - additional jquery markup to inject into widget (see FAQ)
|
26
|
+
// handlers - additional jquery event handlers to add to widget (see FAQ)
|
27
|
+
// state - additional pre-processing for params sent to webservice (see FAQ)
|
28
|
+
//
|
29
|
+
// Sub-classes are required to over-ride one method: self.render(). If you wish to
|
30
|
+
// use a data model, store a subclass of rep.wigets/model in your widget.
|
31
|
+
//
|
32
|
+
repertoire.widget = function(selector, options) {
|
33
|
+
// this object is an abstract class
|
34
|
+
var self = {};
|
35
|
+
|
36
|
+
// private variables
|
37
|
+
var $widget = $(selector);
|
38
|
+
options = options || {};
|
39
|
+
|
40
|
+
// mix in event handling functionality
|
41
|
+
repertoire.events(self, $widget);
|
42
|
+
|
43
|
+
// to run by hand after sub-classes have evaluated
|
44
|
+
self.initialize = function() {
|
45
|
+
// register any custom handlers
|
46
|
+
if (options.handlers !== undefined)
|
47
|
+
register_handlers(options.handlers);
|
48
|
+
|
49
|
+
// load once at beginning
|
50
|
+
self.refresh();
|
51
|
+
}
|
52
|
+
|
53
|
+
//
|
54
|
+
// Refresh model and render into widget
|
55
|
+
//
|
56
|
+
// Integrates state and markup injectors
|
57
|
+
//
|
58
|
+
self.refresh = function() {
|
59
|
+
var callback;
|
60
|
+
|
61
|
+
// pass to custom state processor
|
62
|
+
if (options.state !== undefined)
|
63
|
+
options.state(self);
|
64
|
+
|
65
|
+
callback = function() {
|
66
|
+
// render the widget
|
67
|
+
var markup = self.render.apply(self, arguments);
|
68
|
+
|
69
|
+
// inject any custom markup
|
70
|
+
if (options.injectors !== undefined)
|
71
|
+
// TODO. figure out how to send all args to injectors
|
72
|
+
process_injectors(markup, options.injectors, arguments[0]);
|
73
|
+
|
74
|
+
// paint markup into the dom
|
75
|
+
if (markup)
|
76
|
+
$widget.html(markup);
|
77
|
+
};
|
78
|
+
|
79
|
+
// start rendering
|
80
|
+
self.reload(callback);
|
81
|
+
};
|
82
|
+
|
83
|
+
//
|
84
|
+
// Render and return markup for this widget.
|
85
|
+
//
|
86
|
+
// Three forms are possible:
|
87
|
+
//
|
88
|
+
// (1) Basic... just return a string or jquery object
|
89
|
+
//
|
90
|
+
// self.render = function() {
|
91
|
+
// return 'Hello world!';
|
92
|
+
// };
|
93
|
+
//
|
94
|
+
// (2) If you just want to tweak the superclass' view:
|
95
|
+
//
|
96
|
+
// var template_fn = self.render; // idiom to access super.render()
|
97
|
+
// self.render = function() {
|
98
|
+
// var markup = template_fn();
|
99
|
+
// return $(markup).find('.title').html('New Title');
|
100
|
+
// };
|
101
|
+
//
|
102
|
+
// (3) If you want to modify the DOM in place, do so
|
103
|
+
// and return nothing.
|
104
|
+
//
|
105
|
+
self.render = function() {
|
106
|
+
return $('<div class="rep"/>'); // namespace for all other widget css is 'rep'
|
107
|
+
};
|
108
|
+
|
109
|
+
|
110
|
+
//
|
111
|
+
// A hook for use when your widget must render the results of an ajax callback. Put
|
112
|
+
// the ajax call in self.reload(). Its results will be passed to self.render().
|
113
|
+
//
|
114
|
+
// self.reload = function(callback) {
|
115
|
+
// $.get('http://www.nytimes.com', callback);
|
116
|
+
// };
|
117
|
+
//
|
118
|
+
// self.render = function(daily_news) {
|
119
|
+
// $(daily_news).find('title').text(); // widget's view is the title of the new york times
|
120
|
+
// }
|
121
|
+
//
|
122
|
+
// N.B. In real-world cases, the call in self.reload should be to your
|
123
|
+
// data model class, which serves as an ajax api facade.
|
124
|
+
//
|
125
|
+
self.reload = function(callback) {
|
126
|
+
callback();
|
127
|
+
}
|
128
|
+
|
129
|
+
//
|
130
|
+
// Register a handler for dom events on this widget. Call with an event selector and a standard jquery event
|
131
|
+
// handler function, e.g.
|
132
|
+
//
|
133
|
+
// self.handler('click.toggle_value!.rep .facet .value', function() { ... });
|
134
|
+
//
|
135
|
+
// Note the syntax used to identify a handler's event, namespace, and the jquery selector: '<event.namespace>!<target>'.
|
136
|
+
// Both event and namespace are optional - leave them out to register a click handler with a unique namespace.
|
137
|
+
//
|
138
|
+
// N.B. This method is intended only for protected use within a widget and its subclasses, since it depends
|
139
|
+
// on the view implementation.
|
140
|
+
//
|
141
|
+
self.handler = function(event_selector, fn) {
|
142
|
+
event_selector = parse_event_selector(event_selector);
|
143
|
+
var event = event_selector[0],
|
144
|
+
selector = event_selector[1]; // why doesn't JS support array decomposition?!?
|
145
|
+
|
146
|
+
// bind new handler
|
147
|
+
$widget.bind(event, function (e) {
|
148
|
+
var $el = $(e.target);
|
149
|
+
var result = false;
|
150
|
+
// walk up dom tree for selector
|
151
|
+
while ($el.length > 0) {
|
152
|
+
if ($el.is(selector)) {
|
153
|
+
result = fn.apply($el[0], [e]);
|
154
|
+
if (result === false)
|
155
|
+
e.preventDefault();
|
156
|
+
return;
|
157
|
+
}
|
158
|
+
$el = $el.parent();
|
159
|
+
}
|
160
|
+
});
|
161
|
+
}
|
162
|
+
|
163
|
+
// PRIVATE
|
164
|
+
|
165
|
+
// register a collection of event handlers
|
166
|
+
function register_handlers(handlers) {
|
167
|
+
$.each(handlers, function(selector, handler) {
|
168
|
+
// register each handler
|
169
|
+
self.handler(selector, function() {
|
170
|
+
// bind self as an argument for the custom handler
|
171
|
+
return handler.apply(this, [self]);
|
172
|
+
});
|
173
|
+
});
|
174
|
+
}
|
175
|
+
|
176
|
+
// inject custom markup into widget
|
177
|
+
function process_injectors($markup, injectors, data) {
|
178
|
+
// workaround for jquery find not matching top element
|
179
|
+
$wrapped = $("<div/>").append($markup);
|
180
|
+
|
181
|
+
$.each(injectors, function(selector, injector) {
|
182
|
+
var $elems = $wrapped.find(selector);
|
183
|
+
if ($elems.length > 0) {
|
184
|
+
injector.apply($elems, [ self, data ]);
|
185
|
+
}
|
186
|
+
});
|
187
|
+
}
|
188
|
+
|
189
|
+
// parse an event name and selector spec
|
190
|
+
function parse_event_selector(event_selector) {
|
191
|
+
var s = event_selector.split('!');
|
192
|
+
var event, selector;
|
193
|
+
|
194
|
+
if (s.length === 2) {
|
195
|
+
event = s[0], selector = s[1];
|
196
|
+
} else if (s.length === 1) {
|
197
|
+
event = 'click', selector = s[0];
|
198
|
+
} else {
|
199
|
+
throw "Could not parse event selector: " + event_selector;
|
200
|
+
}
|
201
|
+
|
202
|
+
if (event.indexOf('.')<0) {
|
203
|
+
// create a default namespace from selector or random number
|
204
|
+
namespace = selector.replace(/[^a-zA-z0-9]/g, '') || new Date().getTime();
|
205
|
+
event = event + '.' + namespace;
|
206
|
+
}
|
207
|
+
|
208
|
+
return [event, selector];
|
209
|
+
}
|
210
|
+
|
211
|
+
// end of widget factory function
|
212
|
+
return self;
|
213
|
+
};
|
@@ -0,0 +1,14 @@
|
|
1
|
+
/*
|
2
|
+
* Repertoire abstract ajax widget
|
3
|
+
*
|
4
|
+
* Copyright (c) 2009 MIT Hyperstudio
|
5
|
+
* Christopher York, 09/2009
|
6
|
+
*
|
7
|
+
* Requires jquery 1.3.2+
|
8
|
+
* Support: Firefox 3+ & Safari 4+. IE emphatically not supported.
|
9
|
+
*/
|
10
|
+
|
11
|
+
//= require rep.widgets/global
|
12
|
+
//= require rep.widgets/widget
|
13
|
+
//= require rep.widgets/events
|
14
|
+
//= require rep.widgets/model
|
@@ -14,5 +14,5 @@
|
|
14
14
|
.rep .facet .values .value .label { position: relative; left: 0.5em;}
|
15
15
|
.rep .facet .values .value .label:hover,
|
16
16
|
.rep .facet .nesting_level:hover { cursor: pointer; text-decoration: underline; }
|
17
|
-
.loading .spinner { background: url(
|
17
|
+
.loading .spinner { background: url(/assets/repertoire-faceting/spinner_sm.gif) no-repeat left center; float:right; min-height:15px; min-width:15px;}
|
18
18
|
.loading .mask { background: rgba(255, 255, 255, 0.6); position:absolute; top:0; left:0; width: 100%; height: 100%; }
|
@@ -3,7 +3,7 @@ require 'active_support/ordered_hash'
|
|
3
3
|
module Repertoire
|
4
4
|
module Faceting
|
5
5
|
module PostgreSQLColumn #:nodoc:
|
6
|
-
|
6
|
+
|
7
7
|
# TODO. still not clear how ActiveRecord adapters support adding custom SQL data types...
|
8
8
|
# feels like a monkey-patch, but there's no documented way to accomplish this simple task
|
9
9
|
def simplified_type(field_type)
|
@@ -12,82 +12,141 @@ module Repertoire
|
|
12
12
|
when 'signature'
|
13
13
|
:string
|
14
14
|
else
|
15
|
-
super
|
15
|
+
super
|
16
16
|
end
|
17
17
|
end
|
18
|
-
|
18
|
+
|
19
19
|
end
|
20
|
-
|
20
|
+
|
21
21
|
module PostgreSQLAdapter #:nodoc:
|
22
|
-
|
23
|
-
#
|
24
|
-
|
25
|
-
|
26
|
-
|
22
|
+
|
23
|
+
#
|
24
|
+
# Methods for accessing faceting APIs (as PostgreSQL extensions)
|
25
|
+
#
|
26
|
+
|
27
|
+
# Returns the available in-database faceting APIs (only when installed as an extension)
|
28
|
+
def facet_api_bindings
|
29
|
+
@api_bindings ||= select_values "SELECT name FROM pg_available_extensions WHERE name LIKE 'faceting%'"
|
27
30
|
end
|
28
31
|
|
29
|
-
# Returns the
|
30
|
-
def
|
31
|
-
|
32
|
-
result = select_value(sql)
|
33
|
-
Float(result)
|
32
|
+
# Returns the currently active faceting API binding (only when installed as an extension)
|
33
|
+
def current_facet_api_binding
|
34
|
+
@current_api_binding ||= select_value "SELECT extname FROM pg_extension WHERE extname LIKE 'faceting%';"
|
34
35
|
end
|
35
|
-
|
36
|
-
#
|
37
|
-
|
38
|
-
|
39
|
-
|
36
|
+
|
37
|
+
#
|
38
|
+
# Methods for access to facet indices
|
39
|
+
#
|
40
|
+
|
41
|
+
def facet_schema
|
42
|
+
# TODO. Not clear how to get the schema associated with a PostgreSQL extension from the
|
43
|
+
# system tables. So we limit to loading into a schema named 'facet' for now.
|
44
|
+
'facet'
|
40
45
|
end
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
sql = File.read(Repertoire::Faceting::MODULE_PATH + '/ext/signature.sql')
|
45
|
-
unload_faceting
|
46
|
-
execute(sql)
|
46
|
+
|
47
|
+
def facet_table_name(model_name, name)
|
48
|
+
"#{facet_schema}.#{model_name}_#{name}_index"
|
47
49
|
end
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
50
|
+
|
51
|
+
def indexed_facets(model_name)
|
52
|
+
sql = "SELECT matviewname FROM pg_matviews WHERE schemaname = 'facet'"
|
53
|
+
tables = select_values(sql)
|
54
|
+
|
55
|
+
tables.grep(/#{model_name}_(\w+)_(\d*)?index/) { $1 }
|
52
56
|
end
|
53
|
-
|
54
|
-
#
|
55
|
-
|
56
|
-
|
57
|
-
|
57
|
+
|
58
|
+
#
|
59
|
+
# Methods for managing packed id columns on models
|
60
|
+
#
|
61
|
+
|
62
|
+
def signature_wastage(table_name, faceting_id)
|
63
|
+
sql = "SELECT #{facet_schema}.wastage(#{faceting_id}) FROM #{table_name}"
|
64
|
+
result = select_value(sql)
|
65
|
+
Float(result)
|
58
66
|
end
|
59
|
-
|
67
|
+
|
68
|
+
#
|
69
|
+
# Methods for running facet value counts
|
70
|
+
#
|
71
|
+
|
60
72
|
def population(facet, masks, signatures)
|
61
73
|
# Would be nice to use Arel here... but recent versions (~ 2.0.1) have removed the property of closure under
|
62
74
|
# composition (e.g. joining two select managers / sub-selects)... why?!?
|
63
|
-
sigs = [ '
|
75
|
+
sigs = [ 'fct.signature' ]
|
64
76
|
exprs = masks.map{|mask| "(#{mask.to_sql})"}
|
65
77
|
sigs << 'mask.signature' unless masks.empty?
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
sql
|
78
|
+
|
79
|
+
bit_and = " OPERATOR(#{facet_schema}.&) "
|
80
|
+
|
81
|
+
sql = "SELECT fct.#{facet.facet_name}, #{facet_schema}.count(#{ sigs.join(bit_and) }) "
|
82
|
+
sql += "FROM (#{signatures.to_sql}) AS fct "
|
83
|
+
sql += ", (SELECT (#{exprs.join(bit_and)}) AS signature) AS mask " unless masks.empty?
|
70
84
|
sql += "ORDER BY #{facet.order_values.join(', ')} " if facet.order_values.present?
|
71
85
|
sql += "OFFSET #{facet.offset_value} " if facet.offset_value.present?
|
72
86
|
sql += "LIMIT #{facet.limit_value} " if facet.limit_value.present?
|
73
|
-
|
87
|
+
|
74
88
|
# run query and type cast
|
75
89
|
results = query(sql)
|
76
90
|
results = results.map { |key, count| [ key, count.to_i] }
|
77
91
|
results = ActiveSupport::OrderedHash[results]
|
78
|
-
|
92
|
+
|
79
93
|
# minimums and nils
|
80
|
-
results
|
81
|
-
results.delete(nil)
|
82
|
-
|
94
|
+
results.reject! { |key, count| count < (facet.minimum_value || 1) }
|
95
|
+
results.delete(nil) if facet.nils_value == :exclude
|
96
|
+
|
83
97
|
results
|
84
98
|
end
|
85
|
-
|
99
|
+
|
86
100
|
def mask_members_sql(masks, table_name, faceting_id)
|
101
|
+
bit_and = " OPERATOR(#{facet_schema}.&) "
|
87
102
|
exprs = masks.map { |mask| "(#{mask.to_sql})" }
|
88
|
-
"INNER JOIN members(#{exprs.join(
|
103
|
+
"INNER JOIN #{facet_schema}.members(#{exprs.join(bit_and)}) AS _refinements_id ON (#{table_name}.#{faceting_id} = _refinements_id)"
|
89
104
|
end
|
90
|
-
|
105
|
+
|
106
|
+
|
107
|
+
module MigrationMethods #:nodoc:
|
108
|
+
|
109
|
+
#
|
110
|
+
# Methods used in creating, updating, and removing facet indices
|
111
|
+
#
|
112
|
+
|
113
|
+
def create_materialized_view(view_name, sql)
|
114
|
+
sql = "CREATE MATERIALIZED VIEW #{quote_table_name(view_name)} AS #{sql}"
|
115
|
+
execute(sql)
|
116
|
+
end
|
117
|
+
|
118
|
+
def refresh_materialized_view(view_name)
|
119
|
+
sql = "REFRESH MATERIALIZED VIEW #{quote_table_name(view_name)}"
|
120
|
+
execute(sql)
|
121
|
+
end
|
122
|
+
|
123
|
+
def drop_materialized_view(view_name)
|
124
|
+
sql = "DROP MATERIALIZED VIEW #{quote_table_name(view_name)} CASCADE"
|
125
|
+
execute(sql)
|
126
|
+
end
|
127
|
+
|
128
|
+
# Returns the named PostgreSQL API binding sql; for shared hosts where you cannot build extensions
|
129
|
+
def faceting_api_sql(api_name, schema_name)
|
130
|
+
path = Repertoire::Faceting::MODULE_PATH
|
131
|
+
version = Repertoire::Faceting::VERSION
|
132
|
+
api_name = api_name.to_sym
|
133
|
+
file_name = "#{path}/ext/faceting_#{api_name}--#{version}.sql"
|
134
|
+
|
135
|
+
raise "Use 'CREATE EXTENSION faceting' to load the default facet api" if api_name == :signature
|
136
|
+
raise "Currently, the faceting API must install into a schema named 'facet'" unless schema_name == facet_schema
|
137
|
+
|
138
|
+
# TODO This approach allows production deploys to skip a "rake db:extensions:install" step when installing
|
139
|
+
# to Heroku. In practice, this eases deployment significantly. But shelling out to make during a
|
140
|
+
# Rails migration feels inelegant.
|
141
|
+
system "cd #{path}/ext; make" unless File.exist?(file_name)
|
142
|
+
raise "No API binding available for #{api_name}" unless File.exist?(file_name)
|
143
|
+
|
144
|
+
File.read(file_name).gsub(/@extschema@/, facet_schema)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
include MigrationMethods
|
149
|
+
|
91
150
|
end
|
92
151
|
end
|
93
152
|
end
|