mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-01-17 06:52:27 +01:00
using gem pg_search
This commit is contained in:
parent
7af6f18973
commit
d83e3a8d26
1
Gemfile
1
Gemfile
@ -70,6 +70,7 @@ end
|
|||||||
gem 'seed_dump'
|
gem 'seed_dump'
|
||||||
|
|
||||||
gem 'pg'
|
gem 'pg'
|
||||||
|
gem 'pg_search'
|
||||||
|
|
||||||
gem 'devise', '>= 4.6.0'
|
gem 'devise', '>= 4.6.0'
|
||||||
|
|
||||||
|
@ -287,6 +287,9 @@ GEM
|
|||||||
ruby-rc4
|
ruby-rc4
|
||||||
ttfunk
|
ttfunk
|
||||||
pg (1.2.2)
|
pg (1.2.2)
|
||||||
|
pg_search (2.3.2)
|
||||||
|
activerecord (>= 5.2)
|
||||||
|
activesupport (>= 5.2)
|
||||||
powerpack (0.1.2)
|
powerpack (0.1.2)
|
||||||
prawn (2.2.2)
|
prawn (2.2.2)
|
||||||
pdf-core (~> 0.7.0)
|
pdf-core (~> 0.7.0)
|
||||||
@ -500,6 +503,7 @@ DEPENDENCIES
|
|||||||
openlab_ruby
|
openlab_ruby
|
||||||
pdf-reader
|
pdf-reader
|
||||||
pg
|
pg
|
||||||
|
pg_search
|
||||||
prawn
|
prawn
|
||||||
prawn-table
|
prawn-table
|
||||||
puma (= 3.12.6)
|
puma (= 3.12.6)
|
||||||
|
@ -6,11 +6,7 @@ class Project < ApplicationRecord
|
|||||||
include AASM
|
include AASM
|
||||||
include NotifyWith::NotificationAttachedObject
|
include NotifyWith::NotificationAttachedObject
|
||||||
include OpenlabSync
|
include OpenlabSync
|
||||||
|
include PgSearch::Model
|
||||||
# elastic initialisations
|
|
||||||
include Elasticsearch::Model
|
|
||||||
index_name 'fablab'
|
|
||||||
document_type 'projects'
|
|
||||||
|
|
||||||
# kaminari
|
# kaminari
|
||||||
# -- dependency in app/assets/javascripts/controllers/projects.js.erb
|
# -- dependency in app/assets/javascripts/controllers/projects.js.erb
|
||||||
@ -55,109 +51,21 @@ class Project < ApplicationRecord
|
|||||||
|
|
||||||
# scopes
|
# scopes
|
||||||
scope :published, -> { where("state = 'published'") }
|
scope :published, -> { where("state = 'published'") }
|
||||||
|
pg_search_scope :search,
|
||||||
## elastic
|
against: {
|
||||||
# callbacks
|
name: 'A',
|
||||||
after_save { ProjectIndexerWorker.perform_async(:index, id) }
|
tags: 'B',
|
||||||
after_destroy { ProjectIndexerWorker.perform_async(:delete, id) }
|
description: 'C'
|
||||||
|
},
|
||||||
# mapping
|
associated_against: {
|
||||||
settings index: { number_of_replicas: 0 } do
|
project_steps: {
|
||||||
mappings dynamic: 'true' do
|
title: 'D',
|
||||||
indexes 'state', analyzer: 'simple'
|
description: 'E'
|
||||||
indexes 'tags', analyzer: Rails.application.secrets.elasticsearch_language_analyzer
|
}
|
||||||
indexes 'name', analyzer: Rails.application.secrets.elasticsearch_language_analyzer
|
},
|
||||||
indexes 'description', analyzer: Rails.application.secrets.elasticsearch_language_analyzer
|
using: {
|
||||||
indexes 'project_steps' do
|
tsearch: { dictionary: Rails.application.secrets.postgresql_language_analyzer }
|
||||||
indexes 'title', analyzer: Rails.application.secrets.elasticsearch_language_analyzer
|
}
|
||||||
indexes 'description', analyzer: Rails.application.secrets.elasticsearch_language_analyzer
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# the resulting JSON will be indexed in ElasticSearch, as /fablab/projects
|
|
||||||
def as_indexed_json
|
|
||||||
ApplicationController.new.view_context.render(
|
|
||||||
partial: 'api/projects/indexed',
|
|
||||||
locals: { project: self },
|
|
||||||
formats: [:json],
|
|
||||||
handlers: [:jbuilder]
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.search(params, current_user)
|
|
||||||
connection = ActiveRecord::Base.connection
|
|
||||||
return unless connection.instance_values['config'][:adapter] == 'postgresql'
|
|
||||||
|
|
||||||
# see http://rachbelaid.com/postgres-full-text-search-is-good-enough/
|
|
||||||
return connection.execute <<~SQL
|
|
||||||
SELECT pid, p_name
|
|
||||||
FROM (SELECT projects.id as pid,
|
|
||||||
projects.name as p_name,
|
|
||||||
to_tsvector('#{Rails.application.secrets.postgresql_language_analyzer}', unaccent(projects.tags)) ||
|
|
||||||
to_tsvector('#{Rails.application.secrets.postgresql_language_analyzer}', unaccent(projects.name)) ||
|
|
||||||
to_tsvector('#{Rails.application.secrets.postgresql_language_analyzer}', unaccent(projects.description)) ||
|
|
||||||
to_tsvector('#{Rails.application.secrets.postgresql_language_analyzer}', unaccent(projects.description)) ||
|
|
||||||
to_tsvector('#{Rails.application.secrets.postgresql_language_analyzer}', unaccent(ps.title)) ||
|
|
||||||
to_tsvector('#{Rails.application.secrets.postgresql_language_analyzer}', unaccent(ps.description)) as document
|
|
||||||
FROM projects
|
|
||||||
JOIN project_steps ps on projects.id = ps.project_id) p_search
|
|
||||||
WHERE p_search.document @@ to_tsquery('#{Rails.application.secrets.postgresql_language_analyzer}', unaccent('#{params['q']}'));
|
|
||||||
SQL
|
|
||||||
|
|
||||||
Project.__elasticsearch__.search(build_search_query_from_context(params, current_user))
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.build_search_query_from_context(params, current_user)
|
|
||||||
search = {
|
|
||||||
query: {
|
|
||||||
bool: {
|
|
||||||
must: [],
|
|
||||||
should: [],
|
|
||||||
filter: []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# we sort by created_at if there isn't a query
|
|
||||||
if params['q'].blank?
|
|
||||||
search[:sort] = { created_at: { order: :desc } }
|
|
||||||
else
|
|
||||||
# otherwise we search for the word (q) in various fields
|
|
||||||
search[:query][:bool][:must] << {
|
|
||||||
multi_match: {
|
|
||||||
query: params['q'],
|
|
||||||
type: 'most_fields',
|
|
||||||
fields: %w[tags^4 name^5 description^3 project_steps.title^2 project_steps.description]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
# we filter by themes, components, machines
|
|
||||||
params.each do |name, value|
|
|
||||||
if name =~ /(.+_id$)/
|
|
||||||
search[:query][:bool][:filter] << { term: { "#{name}s" => value } } if value
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# if use select filter 'my project' or 'my collaborations'
|
|
||||||
if current_user && params.key?('from')
|
|
||||||
search[:query][:bool][:filter] << { term: { author_id: current_user.id } } if params['from'] == 'mine'
|
|
||||||
search[:query][:bool][:filter] << { term: { user_ids: current_user.id } } if params['from'] == 'collaboration'
|
|
||||||
end
|
|
||||||
|
|
||||||
# if user is connected, also display his draft projects
|
|
||||||
if current_user
|
|
||||||
search[:query][:bool][:should] << { term: { state: 'published' } }
|
|
||||||
search[:query][:bool][:should] << { term: { author_id: current_user.id } }
|
|
||||||
search[:query][:bool][:should] << { term: { user_ids: current_user.id } }
|
|
||||||
else
|
|
||||||
# otherwise display only published projects
|
|
||||||
search[:query][:bool][:must] << { term: { state: 'published' } }
|
|
||||||
end
|
|
||||||
|
|
||||||
search
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
# Index the projects to ElasticSearch
|
|
||||||
class ProjectIndexerWorker
|
|
||||||
include Sidekiq::Worker
|
|
||||||
sidekiq_options queue: 'elasticsearch', retry: true
|
|
||||||
|
|
||||||
def perform(operation, record_id)
|
|
||||||
logger = Sidekiq.logger.level == Logger::DEBUG ? Sidekiq.logger : nil
|
|
||||||
client = Elasticsearch::Model.client
|
|
||||||
|
|
||||||
logger&.debug [operation, "ID: #{record_id}"]
|
|
||||||
|
|
||||||
case operation.to_s
|
|
||||||
when /index/
|
|
||||||
record = Project.find(record_id)
|
|
||||||
client.index index: Project.index_name, type: Project.document_type, id: record.id, body: record.as_indexed_json
|
|
||||||
when /delete/
|
|
||||||
client.delete index: Project.index_name, type: Project.document_type, id: record_id
|
|
||||||
else raise ArgumentError, "Unknown operation '#{operation}'"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
@ -101,35 +101,6 @@ namespace :fablab do
|
|||||||
}';`
|
}';`
|
||||||
end
|
end
|
||||||
|
|
||||||
desc 'sync all/one project in ElasticSearch index'
|
|
||||||
task :build_projects_index, [:id] => :environment do |_task, args|
|
|
||||||
client = Project.__elasticsearch__.client
|
|
||||||
|
|
||||||
# ask if we delete the index
|
|
||||||
print 'Delete all projects in ElasticSearch index? (y/n) '
|
|
||||||
confirm = STDIN.gets.chomp
|
|
||||||
if confirm == 'y'
|
|
||||||
client.delete_by_query(
|
|
||||||
index: Project.index_name,
|
|
||||||
type: Project.document_type,
|
|
||||||
conflicts: 'proceed',
|
|
||||||
body: { query: { match_all: {} } }
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
# create index if not exists
|
|
||||||
Project.__elasticsearch__.create_index! force: true unless client.indices.exists? index: Project.index_name
|
|
||||||
|
|
||||||
# index requested documents
|
|
||||||
if args.id
|
|
||||||
ProjectIndexerWorker.perform_async(:index, id)
|
|
||||||
else
|
|
||||||
Project.pluck(:id).each do |project_id|
|
|
||||||
ProjectIndexerWorker.perform_async(:index, project_id)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
desc 'sync all/one availabilities in ElasticSearch index'
|
desc 'sync all/one availabilities in ElasticSearch index'
|
||||||
task :build_availabilities_index, [:id] => :environment do |_task, args|
|
task :build_availabilities_index, [:id] => :environment do |_task, args|
|
||||||
client = Availability.__elasticsearch__.client
|
client = Availability.__elasticsearch__.client
|
||||||
|
Loading…
x
Reference in New Issue
Block a user