New and Announcement search. Disabled impression.
This commit is contained in:
parent
033667f99b
commit
f9bdb4a327
|
@ -5,7 +5,8 @@ log/*.log
|
||||||
tmp/**/*
|
tmp/**/*
|
||||||
public/uploads/**/*
|
public/uploads/**/*
|
||||||
uploads/**/*
|
uploads/**/*
|
||||||
|
public/panel/**/*
|
||||||
|
public/index.html
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
*.swp
|
*.swp
|
||||||
|
|
8
Gemfile
8
Gemfile
|
@ -41,9 +41,15 @@ gem 'rb-readline' if RUBY_PLATFORM.downcase.include?("linux")
|
||||||
|
|
||||||
gem "impressionist", :require => "impressionist", :path => "vendor/impressionist"
|
gem "impressionist", :require => "impressionist", :path => "vendor/impressionist"
|
||||||
|
|
||||||
gem "tire"
|
|
||||||
|
|
||||||
|
|
||||||
|
gem 'redis','>= 2.1.1'
|
||||||
|
gem 'chinese_pinyin', '0.4.1'
|
||||||
|
# add rmmseg if you need search by segment words
|
||||||
|
gem 'rmmseg-cpp-huacnlee', '0.2.9'
|
||||||
|
gem 'redis-namespace','~> 1.0.2'
|
||||||
|
gem 'redis-search', '0.7.1'
|
||||||
|
|
||||||
# Gems used only for assets and not required
|
# Gems used only for assets and not required
|
||||||
# in production environments by default.
|
# in production environments by default.
|
||||||
group :assets do
|
group :assets do
|
||||||
|
|
21
Gemfile.lock
21
Gemfile.lock
|
@ -66,6 +66,7 @@ GEM
|
||||||
carrierwave-mongoid (0.1.3)
|
carrierwave-mongoid (0.1.3)
|
||||||
carrierwave (>= 0.5.6)
|
carrierwave (>= 0.5.6)
|
||||||
mongoid (~> 2.1)
|
mongoid (~> 2.1)
|
||||||
|
chinese_pinyin (0.4.1)
|
||||||
chronic (0.6.7)
|
chronic (0.6.7)
|
||||||
coffee-rails (3.1.1)
|
coffee-rails (3.1.1)
|
||||||
coffee-script (>= 2.2.0)
|
coffee-script (>= 2.2.0)
|
||||||
|
@ -96,7 +97,6 @@ GEM
|
||||||
railties (>= 3.0.0)
|
railties (>= 3.0.0)
|
||||||
fastercsv (1.5.4)
|
fastercsv (1.5.4)
|
||||||
haml (3.1.4)
|
haml (3.1.4)
|
||||||
hashr (0.0.21)
|
|
||||||
hike (1.2.1)
|
hike (1.2.1)
|
||||||
hoe (2.16.1)
|
hoe (2.16.1)
|
||||||
rake (~> 0.8)
|
rake (~> 0.8)
|
||||||
|
@ -176,6 +176,10 @@ GEM
|
||||||
redis (2.2.2)
|
redis (2.2.2)
|
||||||
redis-namespace (1.0.3)
|
redis-namespace (1.0.3)
|
||||||
redis (< 3.0.0)
|
redis (< 3.0.0)
|
||||||
|
redis-search (0.7.1)
|
||||||
|
chinese_pinyin (>= 0.3.0)
|
||||||
|
redis (>= 2.1.1)
|
||||||
|
redis-namespace (~> 1.0.2)
|
||||||
resque (1.20.0)
|
resque (1.20.0)
|
||||||
multi_json (~> 1.0)
|
multi_json (~> 1.0)
|
||||||
redis-namespace (~> 1.0.2)
|
redis-namespace (~> 1.0.2)
|
||||||
|
@ -187,8 +191,7 @@ GEM
|
||||||
redis (>= 2.0.1)
|
redis (>= 2.0.1)
|
||||||
resque (>= 1.8.0)
|
resque (>= 1.8.0)
|
||||||
rufus-scheduler
|
rufus-scheduler
|
||||||
rest-client (1.6.7)
|
rmmseg-cpp-huacnlee (0.2.9)
|
||||||
mime-types (>= 1.16)
|
|
||||||
rsolr (1.0.8)
|
rsolr (1.0.8)
|
||||||
builder (>= 2.1.2)
|
builder (>= 2.1.2)
|
||||||
rspec (2.8.0)
|
rspec (2.8.0)
|
||||||
|
@ -261,12 +264,6 @@ GEM
|
||||||
tilt (1.3.3)
|
tilt (1.3.3)
|
||||||
tinymce-rails (3.4.8)
|
tinymce-rails (3.4.8)
|
||||||
railties (>= 3.1)
|
railties (>= 3.1)
|
||||||
tire (0.4.2)
|
|
||||||
activemodel (>= 3.0)
|
|
||||||
hashr (~> 0.0.19)
|
|
||||||
multi_json (~> 1.0)
|
|
||||||
rake
|
|
||||||
rest-client (~> 1.6)
|
|
||||||
transaction-simple (1.4.0)
|
transaction-simple (1.4.0)
|
||||||
hoe (>= 1.1.7)
|
hoe (>= 1.1.7)
|
||||||
treetop (1.4.10)
|
treetop (1.4.10)
|
||||||
|
@ -299,6 +296,7 @@ DEPENDENCIES
|
||||||
bson_ext
|
bson_ext
|
||||||
carrierwave
|
carrierwave
|
||||||
carrierwave-mongoid
|
carrierwave-mongoid
|
||||||
|
chinese_pinyin (= 0.4.1)
|
||||||
coffee-rails
|
coffee-rails
|
||||||
database_cleaner
|
database_cleaner
|
||||||
delorean
|
delorean
|
||||||
|
@ -322,9 +320,13 @@ DEPENDENCIES
|
||||||
radius
|
radius
|
||||||
rails (>= 3.1.0, < 3.2.0)
|
rails (>= 3.1.0, < 3.2.0)
|
||||||
rake
|
rake
|
||||||
|
redis (>= 2.1.1)
|
||||||
|
redis-namespace (~> 1.0.2)
|
||||||
|
redis-search (= 0.7.1)
|
||||||
resque
|
resque
|
||||||
resque-restriction
|
resque-restriction
|
||||||
resque-scheduler
|
resque-scheduler
|
||||||
|
rmmseg-cpp-huacnlee (= 0.2.9)
|
||||||
rspec (~> 2.0)
|
rspec (~> 2.0)
|
||||||
rspec-rails (~> 2.0)
|
rspec-rails (~> 2.0)
|
||||||
ruby-debug19
|
ruby-debug19
|
||||||
|
@ -337,6 +339,5 @@ DEPENDENCIES
|
||||||
sprockets
|
sprockets
|
||||||
sunspot-rails-tester
|
sunspot-rails-tester
|
||||||
tinymce-rails
|
tinymce-rails
|
||||||
tire
|
|
||||||
uglifier
|
uglifier
|
||||||
watchr
|
watchr
|
||||||
|
|
|
@ -19,7 +19,7 @@ class PagesController < ApplicationController
|
||||||
#begin
|
#begin
|
||||||
@item = Item.first(:conditions => {:path => params[:page_name]})
|
@item = Item.first(:conditions => {:path => params[:page_name]})
|
||||||
if @item && @item.is_published && (@item.enabled_for.nil? ? true : @item.enabled_for.include?(I18n.locale.to_s))
|
if @item && @item.is_published && (@item.enabled_for.nil? ? true : @item.enabled_for.include?(I18n.locale.to_s))
|
||||||
impressionist(@item)
|
# impressionist(@item)
|
||||||
case @item._type
|
case @item._type
|
||||||
when 'Page'
|
when 'Page'
|
||||||
render_page
|
render_page
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
require "redis"
|
||||||
|
require "redis-namespace"
|
||||||
|
require "redis-search"
|
||||||
|
# don't forget change namespace
|
||||||
|
redis = Redis.new(:host => "127.0.0.1",:port => "6379")
|
||||||
|
# We suggest you use a special db in Redis, when you need to clear all data, you can use flushdb command to clear them.
|
||||||
|
redis.select(3)
|
||||||
|
# Give a special namespace as prefix for Redis key, when your have more than one project used redis-search, this config will make them work fine.
|
||||||
|
redis = Redis::Namespace.new("orbit_nccu:redis_search2", :redis => redis)
|
||||||
|
Redis::Search.configure do |config|
|
||||||
|
config.redis = redis
|
||||||
|
config.complete_max_length = 100
|
||||||
|
config.pinyin_match = true
|
||||||
|
# use rmmseg, true to disable it, it can save memroy
|
||||||
|
config.disable_rmmseg = false
|
||||||
|
end
|
||||||
|
Bulletin.new
|
||||||
|
NewsBulletin.new
|
|
@ -0,0 +1,11 @@
|
||||||
|
# encoding: UTF-8
|
||||||
|
namespace :matt_dev do
|
||||||
|
desc 'Testing Searching'
|
||||||
|
task :searching_01 => :environment do
|
||||||
|
a= Redis::Search.query("Bulletin",'我',:conditions =>{:is_checked=>true,:is_hidden=>false})
|
||||||
|
p a
|
||||||
|
a= Redis::Search.query("NewsBulletin",'社科院',:conditions =>{:is_checked=>true,:is_hidden=>false})
|
||||||
|
p a
|
||||||
|
# p Pinyin.t('台灣不是中國的一部分')
|
||||||
|
end
|
||||||
|
end
|
885
tmp/tire-dsl.rb
885
tmp/tire-dsl.rb
|
@ -1,885 +0,0 @@
|
||||||
# encoding: UTF-8
|
|
||||||
#
|
|
||||||
# **Tire** provides rich and comfortable Ruby API for the
|
|
||||||
# [_ElasticSearch_](http://www.elasticsearch.org/) search engine/database.
|
|
||||||
#
|
|
||||||
# _ElasticSearch_ is a scalable, distributed, cloud-ready, highly-available
|
|
||||||
# full-text search engine and database, communicating by JSON over RESTful HTTP,
|
|
||||||
# based on [Lucene](http://lucene.apache.org/), written in Java.
|
|
||||||
#
|
|
||||||
# <img src="http://github.com/favicon.ico" style="position:relative; top:2px">
|
|
||||||
# _Tire_ is open source, and you can download or clone the source code
|
|
||||||
# from <https://github.com/karmi/tire>.
|
|
||||||
#
|
|
||||||
# By following these instructions you should have the search running
|
|
||||||
# on a sane operation system in less then 10 minutes.
|
|
||||||
|
|
||||||
# Note, that this file can be executed directly:
|
|
||||||
#
|
|
||||||
# ruby -I lib examples/tire-dsl.rb
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
#### Installation
|
|
||||||
|
|
||||||
# Install _Tire_ with _Rubygems_:
|
|
||||||
|
|
||||||
#
|
|
||||||
# gem install tire
|
|
||||||
#
|
|
||||||
require 'rubygems'
|
|
||||||
require 'colorize'
|
|
||||||
|
|
||||||
# _Tire_ uses the [_multi_json_](https://github.com/intridea/multi_json) gem as a generic JSON library.
|
|
||||||
# We want to use the [_yajl-ruby_](https://github.com/brianmario/yajl-ruby) gem in its full on mode here.
|
|
||||||
#
|
|
||||||
require 'yajl/json_gem'
|
|
||||||
|
|
||||||
# Now, let's require the _Tire_ gem itself, and we're ready to go.
|
|
||||||
#
|
|
||||||
require 'tire'
|
|
||||||
|
|
||||||
#### Prerequisites
|
|
||||||
|
|
||||||
# We'll need a working and running _ElasticSearch_ server, of course. Thankfully, that's easy.
|
|
||||||
( puts <<-"INSTALL" ; exit(1) ) unless (RestClient.get('http://localhost:9200') rescue false)
|
|
||||||
|
|
||||||
[ERROR] You don’t appear to have ElasticSearch installed. Please install and launch it with the following commands:
|
|
||||||
|
|
||||||
curl -k -L -o elasticsearch-0.19.0.tar.gz http://github.com/downloads/elasticsearch/elasticsearch/elasticsearch-0.19.0.tar.gz
|
|
||||||
tar -zxvf elasticsearch-0.19.0.tar.gz
|
|
||||||
./elasticsearch-0.19.0/bin/elasticsearch -f
|
|
||||||
INSTALL
|
|
||||||
|
|
||||||
### Storing and indexing documents
|
|
||||||
|
|
||||||
# Let's initialize an index named “articles”.
|
|
||||||
#
|
|
||||||
Tire.index 'articles' do
|
|
||||||
# To make sure it's fresh, let's delete any existing index with the same name.
|
|
||||||
#
|
|
||||||
delete
|
|
||||||
# And then, let's create it.
|
|
||||||
#
|
|
||||||
create
|
|
||||||
|
|
||||||
# We want to store and index some articles with `title`, `tags` and `published_on` properties.
|
|
||||||
# Simple Hashes are OK. The default type is „document”.
|
|
||||||
#
|
|
||||||
store :title => '復興「校球」 政大男足決戰UFA足球聯賽', :tags => ['足球'], :published_on => '2011-01-01'
|
|
||||||
store :title => '社科院舉辦碩博士班畢業生撥穗典禮', :tags => ['博士班', '畢業'], :published_on => '2011-01-02'
|
|
||||||
|
|
||||||
# We usually want to set a specific _type_ for the document in _ElasticSearch_.
|
|
||||||
# Simply setting a `type` property is OK.
|
|
||||||
#
|
|
||||||
store :type => 'article',
|
|
||||||
:title => '支持政大學子 羅家倫之女設立獎學金',
|
|
||||||
:tags => ['獎學金'],
|
|
||||||
:published_on => '2011-01-02'
|
|
||||||
|
|
||||||
# We may want to wrap your data in a Ruby class, and use it when storing data.
|
|
||||||
# The contract required of such a class is very simple.
|
|
||||||
#
|
|
||||||
class Article
|
|
||||||
|
|
||||||
#
|
|
||||||
attr_reader :title, :tags, :published_on
|
|
||||||
def initialize(attributes={})
|
|
||||||
@attributes = attributes
|
|
||||||
@attributes.each_pair { |name,value| instance_variable_set :"@#{name}", value }
|
|
||||||
end
|
|
||||||
|
|
||||||
# It must provide a `type`, `_type` or `document_type` method for propper mapping.
|
|
||||||
#
|
|
||||||
def type
|
|
||||||
'article'
|
|
||||||
end
|
|
||||||
|
|
||||||
# And it must provide a `to_indexed_json` method for conversion to JSON.
|
|
||||||
#
|
|
||||||
def to_indexed_json
|
|
||||||
@attributes.to_json
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Note: Since our class takes a Hash of attributes on initialization, we may even
|
|
||||||
# wrap the results in instances of this class; we'll see how to do that further below.
|
|
||||||
#
|
|
||||||
article = Article.new :title => '親身感受臺灣特色 日本田野研究團政大學習',
|
|
||||||
:tags => ['臺灣特色', '日本'],
|
|
||||||
:published_on => '2011-01-03'
|
|
||||||
|
|
||||||
# Let's store the `article`, now.
|
|
||||||
#
|
|
||||||
store article
|
|
||||||
|
|
||||||
# And let's „force refresh“ the index, so we can query it immediately.
|
|
||||||
#
|
|
||||||
refresh
|
|
||||||
end
|
|
||||||
|
|
||||||
# We may want to define a specific [mapping](http://www.elasticsearch.org/guide/reference/api/admin-indices-create-index.html)
|
|
||||||
# for the index.
|
|
||||||
|
|
||||||
Tire.index 'articles' do
|
|
||||||
# To do so, let's just pass a Hash containing the specified mapping to the `Index#create` method.
|
|
||||||
#
|
|
||||||
create :mappings => {
|
|
||||||
|
|
||||||
# Let's specify for which _type_ of documents this mapping should be used:
|
|
||||||
# „article”, in our case.
|
|
||||||
#
|
|
||||||
:article => {
|
|
||||||
:properties => {
|
|
||||||
|
|
||||||
# Let's specify the type of the field, whether it should be analyzed, ...
|
|
||||||
#
|
|
||||||
:id => { :type => 'string', :index => 'not_analyzed', :include_in_all => false },
|
|
||||||
|
|
||||||
# ... set the boost or analyzer settings for the field, etc. The _ElasticSearch_ guide
|
|
||||||
# has [more information](http://elasticsearch.org/guide/reference/mapping/index.html).
|
|
||||||
# Don't forget, that proper mapping is key to efficient and effective search.
|
|
||||||
# But don't fret about getting the mapping right the first time, you won't.
|
|
||||||
# In most cases, the default, dynamic mapping is just fine for prototyping.
|
|
||||||
#
|
|
||||||
:title => { :type => 'string', :analyzer => 'cjk', :boost => 2.0 },
|
|
||||||
:tags => { :type => 'string', :analyzer => 'keyword' },
|
|
||||||
:content => { :type => 'string', :analyzer => 'cjk' }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
#### Bulk Indexing
|
|
||||||
|
|
||||||
# Of course, we may have large amounts of data, and adding them to the index one by one really isn't the best idea.
|
|
||||||
# We can use _ElasticSearch's_ [bulk API](http://www.elasticsearch.org/guide/reference/api/bulk.html)
|
|
||||||
# for importing the data.
|
|
||||||
|
|
||||||
# So, for demonstration purposes, let's suppose we have a simple collection of hashes to store.
|
|
||||||
#
|
|
||||||
articles = [
|
|
||||||
|
|
||||||
# Notice that such objects must have an `id` property!
|
|
||||||
#
|
|
||||||
{ :id => '1', :type => 'article', :title => '復興「校球」 政大男足決戰UFA足球聯賽', :tags => ['足球'], :published_on => '2011-01-01' },
|
|
||||||
|
|
||||||
# And, of course, they should contain the `type` property for the mapping to work!
|
|
||||||
#
|
|
||||||
{ :id => '2', :type => 'article', :title => '社科院舉辦碩博士班畢業生撥穗典禮', :tags => ['博士班', '畢業','社科院'], :published_on => '2011-01-02' },
|
|
||||||
{ :id => '3', :type => 'article', :title => '支持政大學子 羅家倫之女設立獎學金', :tags => ['獎學金'], :published_on => '2011-01-02' },
|
|
||||||
{ :id => '4', :type => 'article', :title => '親身感受臺灣特色 日本田野研究團政大學習', :tags => ['臺灣特色', '日本'], :published_on => '2011-01-03' }
|
|
||||||
]
|
|
||||||
|
|
||||||
# We can just push them into the index in one go.
|
|
||||||
#
|
|
||||||
Tire.index 'articles' do
|
|
||||||
import articles
|
|
||||||
end
|
|
||||||
|
|
||||||
# Of course, we can easily manipulate the documents before storing them in the index.
|
|
||||||
#
|
|
||||||
Tire.index 'articles' do
|
|
||||||
delete
|
|
||||||
|
|
||||||
# ... by passing a block to the `import` method. The collection will
|
|
||||||
# be available in the block argument.
|
|
||||||
#
|
|
||||||
import articles do |documents|
|
|
||||||
|
|
||||||
# We will capitalize every _title_ and return the manipulated collection
|
|
||||||
# back to the `import` method.
|
|
||||||
#
|
|
||||||
documents.map { |document| document.update(:title => document[:title].capitalize) }
|
|
||||||
end
|
|
||||||
|
|
||||||
refresh
|
|
||||||
end
|
|
||||||
|
|
||||||
### Searching
|
|
||||||
|
|
||||||
# With the documents indexed and stored in the _ElasticSearch_ database, we can search them, finally.
|
|
||||||
#
|
|
||||||
# _Tire_ exposes the search interface via simple domain-specific language.
|
|
||||||
|
|
||||||
#### Simple Query String Searches
|
|
||||||
|
|
||||||
# We can do simple searches, like searching for articles containing “One” in their title.
|
|
||||||
#
|
|
||||||
s = Tire.search('news_bulletins') do
|
|
||||||
query do
|
|
||||||
string "title:政大"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# The results:
|
|
||||||
# * One [tags: ruby]
|
|
||||||
#
|
|
||||||
s.results.each do |document|
|
|
||||||
puts "Test1==============================Has results: #{s.results.count}".yellow
|
|
||||||
puts "* #{ document.title } [tags: ]"
|
|
||||||
end
|
|
||||||
|
|
||||||
# Or, we can search for articles published between January, 1st and January, 2nd.
|
|
||||||
#
|
|
||||||
puts "Test2==Or, we can search for articles published between January, 1st and January, 2nd.=Has results: #{s.results.count}".yellow
|
|
||||||
s = Tire.search('articles') do
|
|
||||||
query do
|
|
||||||
string "published_on:[2011-01-01 TO 2011-01-02]"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# The results:
|
|
||||||
# * One [published: 2011-01-01]
|
|
||||||
# * Two [published: 2011-01-02]
|
|
||||||
# * Three [published: 2011-01-02]
|
|
||||||
#
|
|
||||||
s.results.each do |document|
|
|
||||||
puts "* #{ document.title } [published: #{document.published_on}]"
|
|
||||||
end
|
|
||||||
|
|
||||||
# Notice, that we can access local variables from the _enclosing scope_.
|
|
||||||
# (Of course, we may write the blocks in shorter notation.)
|
|
||||||
|
|
||||||
# We will define the query in a local variable named `q`...
|
|
||||||
#
|
|
||||||
q = "title:T*"
|
|
||||||
# ... and we can use it inside the `query` block.
|
|
||||||
#
|
|
||||||
s = Tire.search('articles') { query { string q } }
|
|
||||||
|
|
||||||
# The results:
|
|
||||||
# * Two [tags: ruby, python]
|
|
||||||
# * Three [tags: java]
|
|
||||||
#
|
|
||||||
puts "Test3==and we can use it inside the `query` block..[ #{q} ]=Has results: #{s.results.count}".yellow
|
|
||||||
s.results.each do |document|
|
|
||||||
puts "* #{ document.title } [tags:]"
|
|
||||||
end
|
|
||||||
|
|
||||||
# Often, we need to access variables or methods defined in the _outer scope_.
|
|
||||||
# To do that, we have to use a slight variation of the DSL.
|
|
||||||
#
|
|
||||||
|
|
||||||
# Let's assume we have a plain Ruby class, named `Article`.
|
|
||||||
#
|
|
||||||
class Article
|
|
||||||
|
|
||||||
# We will define the query in a class method...
|
|
||||||
#
|
|
||||||
def self.q
|
|
||||||
"title:T*"
|
|
||||||
end
|
|
||||||
|
|
||||||
# ... and wrap the _Tire_ search method in another one.
|
|
||||||
def self.search
|
|
||||||
|
|
||||||
# Notice how we pass the `search` object around as a block argument.
|
|
||||||
#
|
|
||||||
Tire.search('articles') do |search|
|
|
||||||
|
|
||||||
# And we pass the query object in a similar matter.
|
|
||||||
#
|
|
||||||
search.query do |query|
|
|
||||||
|
|
||||||
# Which means we can access the `q` class method.
|
|
||||||
#
|
|
||||||
query.string self.q
|
|
||||||
end
|
|
||||||
end.results
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# We may use any valid [Lucene query syntax](http://lucene.apache.org/java/3_0_3/queryparsersyntax.html)
|
|
||||||
# for the `query_string` queries.
|
|
||||||
|
|
||||||
# For debugging our queries, we can display the JSON which is being sent to _ElasticSearch_.
|
|
||||||
#
|
|
||||||
# {"query":{"query_string":{"query":"title:T*"}}}
|
|
||||||
#
|
|
||||||
puts "", "Query:", "-"*80
|
|
||||||
puts s.to_json.green
|
|
||||||
|
|
||||||
# Or better yet, we may display a complete `curl` command to recreate the request in terminal,
|
|
||||||
# so we can see the naked response, tweak request parameters and meditate on problems.
|
|
||||||
#
|
|
||||||
# curl -X POST "http://localhost:9200/articles/_search?pretty=true" \
|
|
||||||
# -d '{"query":{"query_string":{"query":"title:T*"}}}'
|
|
||||||
#
|
|
||||||
puts "", "Try the query in Curl:", "-"*80
|
|
||||||
puts s.to_curl.green
|
|
||||||
|
|
||||||
|
|
||||||
### Logging
|
|
||||||
|
|
||||||
# For debugging more complex situations, we can enable logging, so requests and responses
|
|
||||||
# will be logged using this `curl`-friendly format.
|
|
||||||
|
|
||||||
Tire.configure do
|
|
||||||
|
|
||||||
# By default, at the _info_ level, only the `curl`-format of request and
|
|
||||||
# basic information about the response will be logged:
|
|
||||||
#
|
|
||||||
# # 2011-04-24 11:34:01:150 [CREATE] ("articles")
|
|
||||||
# #
|
|
||||||
# curl -X POST "http://localhost:9200/articles"
|
|
||||||
#
|
|
||||||
# # 2011-04-24 11:34:01:152 [200]
|
|
||||||
#
|
|
||||||
logger 'elasticsearch.log'
|
|
||||||
|
|
||||||
# For debugging, we can switch to the _debug_ level, which will log the complete JSON responses.
|
|
||||||
#
|
|
||||||
# That's very convenient if we want to post a recreation of some problem or solution
|
|
||||||
# to the mailing list, IRC channel, etc.
|
|
||||||
#
|
|
||||||
logger 'elasticsearch.log', :level => 'debug'
|
|
||||||
|
|
||||||
# Note that we can pass any [`IO`](http://www.ruby-doc.org/core/classes/IO.html)-compatible Ruby object as a logging device.
|
|
||||||
#
|
|
||||||
logger STDERR
|
|
||||||
end
|
|
||||||
|
|
||||||
### Configuration
|
|
||||||
|
|
||||||
# As we have just seen with logging, we can configure various parts of _Tire_.
|
|
||||||
#
|
|
||||||
Tire.configure do
|
|
||||||
|
|
||||||
# First of all, we can configure the URL for _ElasticSearch_.
|
|
||||||
#
|
|
||||||
url "http://search.example.com"
|
|
||||||
|
|
||||||
# Second, we may want to wrap the result items in our own class, for instance
|
|
||||||
# the `Article` class set above.
|
|
||||||
#
|
|
||||||
wrapper Article
|
|
||||||
|
|
||||||
# Finally, we can reset one or all configuration settings to their defaults.
|
|
||||||
#
|
|
||||||
reset :url
|
|
||||||
reset
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
### Complex Searching
|
|
||||||
|
|
||||||
# Query strings are convenient for simple searches, but we may want to define our queries more expressively,
|
|
||||||
# using the _ElasticSearch_ [Query DSL](http://www.elasticsearch.org/guide/reference/query-dsl/index.html).
|
|
||||||
#
|
|
||||||
s = Tire.search('articles') do
|
|
||||||
|
|
||||||
# Let's suppose we want to search for articles with specific _tags_, in our case “ruby” _or_ “python”.
|
|
||||||
#
|
|
||||||
query do
|
|
||||||
|
|
||||||
# That's a great excuse to use a [_terms_](http://elasticsearch.org/guide/reference/query-dsl/terms-query.html)
|
|
||||||
# query.
|
|
||||||
#
|
|
||||||
terms :tags, ['ruby', 'python']
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# The search, as expected, returns three articles, all tagged “ruby” — among other tags:
|
|
||||||
#
|
|
||||||
# * Two [tags: ruby, python]
|
|
||||||
# * One [tags: ruby]
|
|
||||||
# * Four [tags: ruby, php]
|
|
||||||
#
|
|
||||||
puts "Test4==The search, as expected, returns three articles, all tagged “STHs” — among other tags.Has results: #{s.results.count}".yellow
|
|
||||||
s.results.each do |document|
|
|
||||||
puts "* #{ document.title } [tags: ]"
|
|
||||||
end
|
|
||||||
|
|
||||||
# What if we wanted to search for articles tagged both “ruby” _and_ “python”?
|
|
||||||
#
|
|
||||||
s = Tire.search('articles') do
|
|
||||||
query do
|
|
||||||
|
|
||||||
# That's a great excuse to specify `minimum_match` for the query.
|
|
||||||
#
|
|
||||||
terms :tags, ['ruby', 'python'], :minimum_match => 2
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# The search, as expected, returns one article, tagged with _both_ “ruby” and “python”:
|
|
||||||
#
|
|
||||||
# * Two [tags: ruby, python]
|
|
||||||
#
|
|
||||||
puts "Test5==The search, as expected, returns one article, tagged with _both_ 'ruby' and 'python'.Has results: #{s.results.count}".yellow
|
|
||||||
s.results.each do |document|
|
|
||||||
puts "* #{ document.title } [tags: ]"
|
|
||||||
end
|
|
||||||
|
|
||||||
#### Boolean Queries
|
|
||||||
|
|
||||||
# Quite often, we need complex queries with boolean logic.
|
|
||||||
# Instead of composing long query strings such as `tags:ruby OR tags:java AND NOT tags:python`,
|
|
||||||
# we can use the [_bool_](http://www.elasticsearch.org/guide/reference/query-dsl/bool-query.html)
|
|
||||||
# query.
|
|
||||||
|
|
||||||
s = Tire.search('news_bulletins') do
|
|
||||||
query do
|
|
||||||
|
|
||||||
# In _Tire_, we can build `bool` queries declaratively, as usual.
|
|
||||||
boolean do
|
|
||||||
|
|
||||||
# Let's define a `should` (`OR`) query for _ruby_,
|
|
||||||
#
|
|
||||||
should { string 'title:政大' }
|
|
||||||
|
|
||||||
# as well as for _java_,
|
|
||||||
must_not { string 'title:復興' }
|
|
||||||
|
|
||||||
# while defining a `must_not` (`AND NOT`) query for _python_.
|
|
||||||
# must_not { string 'tags:python' }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# The search returns these documents:
|
|
||||||
#
|
|
||||||
# * One [tags: ruby]
|
|
||||||
# * Three [tags: java]
|
|
||||||
# * Four [tags: ruby, php]
|
|
||||||
puts "Test6==Boolean Queries.Has results: #{s.results.count}".yellow
|
|
||||||
s.results.each do |document|
|
|
||||||
puts "* #{ document.title } [tags: ]"
|
|
||||||
end
|
|
||||||
|
|
||||||
puts "Test7== mix and reuse Boolean Queries: #{s.results.count}".yellow
|
|
||||||
# The best thing about `boolean` queries is that we can very easily save these partial queries as Ruby blocks,
|
|
||||||
# to mix and reuse them later, since we can call the `boolean` method multiple times.
|
|
||||||
#
|
|
||||||
|
|
||||||
# Let's define the query for the _tags_ property,
|
|
||||||
#
|
|
||||||
tags_query = lambda do |boolean|
|
|
||||||
boolean.should { string 'tags:ruby' }
|
|
||||||
boolean.should { string 'tags:java' }
|
|
||||||
end
|
|
||||||
|
|
||||||
# ... and a query for the _published_on_ property.
|
|
||||||
published_on_query = lambda do |boolean|
|
|
||||||
boolean.must { string 'published_on:[2011-01-01 TO 2011-01-02]' }
|
|
||||||
end
|
|
||||||
|
|
||||||
# Now, we can use the `tags_query` on its own.
|
|
||||||
#
|
|
||||||
Tire.search('articles') { query { boolean &tags_query } }
|
|
||||||
|
|
||||||
# Or, we can combine it with the `published_on` query.
|
|
||||||
#
|
|
||||||
Tire.search('articles') do
|
|
||||||
query do
|
|
||||||
boolean &tags_query
|
|
||||||
boolean &published_on_query
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# _ElasticSearch_ supports many types of [queries](http://www.elasticsearch.org/guide/reference/query-dsl/).
|
|
||||||
#
|
|
||||||
# Eventually, _Tire_ will support all of them. So far, only these are supported:
|
|
||||||
#
|
|
||||||
# * [string](http://www.elasticsearch.org/guide/reference/query-dsl/query-string-query.html)
|
|
||||||
# * [text](http://www.elasticsearch.org/guide/reference/query-dsl/text-query.html)
|
|
||||||
# * [term](http://elasticsearch.org/guide/reference/query-dsl/term-query.html)
|
|
||||||
# * [terms](http://elasticsearch.org/guide/reference/query-dsl/terms-query.html)
|
|
||||||
# * [bool](http://www.elasticsearch.org/guide/reference/query-dsl/bool-query.html)
|
|
||||||
# * [custom_score](http://www.elasticsearch.org/guide/reference/query-dsl/custom-score-query.html)
|
|
||||||
# * [fuzzy](http://www.elasticsearch.org/guide/reference/query-dsl/fuzzy-query.html)
|
|
||||||
# * [all](http://www.elasticsearch.org/guide/reference/query-dsl/match-all-query.html)
|
|
||||||
# * [ids](http://www.elasticsearch.org/guide/reference/query-dsl/ids-query.html)
|
|
||||||
|
|
||||||
puts "Topic#### Faceted Search ==> SKIP".yellow
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# # _ElasticSearch_ makes it trivial to retrieve complex aggregated data from our index/database,
|
|
||||||
# # so called [_facets_](http://www.elasticsearch.org/guide/reference/api/search/facets/index.html).
|
|
||||||
|
|
||||||
# # Let's say we want to display article counts for every tag in the database.
|
|
||||||
# # For that, we'll use a _terms_ facet.
|
|
||||||
|
|
||||||
# #
|
|
||||||
# s = Tire.search 'articles' do
|
|
||||||
|
|
||||||
# # We will search for articles whose title begins with letter “T”,
|
|
||||||
# #
|
|
||||||
# query { string 'title:T*' }
|
|
||||||
|
|
||||||
# # and retrieve the counts “bucketed” by `tags`.
|
|
||||||
# #
|
|
||||||
# facet 'tags' do
|
|
||||||
# terms :tags
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
|
|
||||||
# # As we see, our query has found two articles, and if you recall our articles from above,
|
|
||||||
# # _Two_ is tagged with “ruby” and “python”, while _Three_ is tagged with “java”.
|
|
||||||
# #
|
|
||||||
# # Found 2 articles: Three, Two
|
|
||||||
# #
|
|
||||||
# # The counts shouldn't surprise us:
|
|
||||||
# #
|
|
||||||
# # Counts by tag:
|
|
||||||
# # -------------------------
|
|
||||||
# # ruby 1
|
|
||||||
# # python 1
|
|
||||||
# # java 1
|
|
||||||
# #
|
|
||||||
# puts "Found #{s.results.count} articles: #{s.results.map(&:title).join(', ')}"
|
|
||||||
# puts "Counts by tag:", "-"*25
|
|
||||||
# s.results.facets['tags']['terms'].each do |f|
|
|
||||||
# puts "#{f['term'].ljust(10)} #{f['count']}"
|
|
||||||
# end
|
|
||||||
|
|
||||||
# # These counts are based on the scope of our current query.
|
|
||||||
# # What if we wanted to display aggregated counts by `tags` across the whole database?
|
|
||||||
|
|
||||||
# #
|
|
||||||
# s = Tire.search 'articles' do
|
|
||||||
|
|
||||||
# # Let's repeat the search for “T”...
|
|
||||||
# #
|
|
||||||
# query { string 'title:T*' }
|
|
||||||
|
|
||||||
# facet 'global-tags', :global => true do
|
|
||||||
|
|
||||||
# # ...but set the `global` scope for the facet in this case.
|
|
||||||
# #
|
|
||||||
# terms :tags
|
|
||||||
# end
|
|
||||||
|
|
||||||
# # We can even _combine_ facets scoped to the current query
|
|
||||||
# # with globally scoped facets — we'll just use a different name.
|
|
||||||
# #
|
|
||||||
# facet 'current-tags' do
|
|
||||||
# terms :tags
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
|
|
||||||
# # Aggregated results for the current query are the same as previously:
|
|
||||||
# #
|
|
||||||
# # Current query facets:
|
|
||||||
# # -------------------------
|
|
||||||
# # ruby 1
|
|
||||||
# # python 1
|
|
||||||
# # java 1
|
|
||||||
# #
|
|
||||||
# puts "Current query facets:", "-"*25
|
|
||||||
# s.results.facets['current-tags']['terms'].each do |f|
|
|
||||||
# puts "#{f['term'].ljust(10)} #{f['count']}"
|
|
||||||
# end
|
|
||||||
|
|
||||||
# # On the other hand, aggregated results for the global scope include also
|
|
||||||
# # tags for articles not matched by the query, such as “java” or “php”:
|
|
||||||
# #
|
|
||||||
# # Global facets:
|
|
||||||
# # -------------------------
|
|
||||||
# # ruby 3
|
|
||||||
# # python 1
|
|
||||||
# # php 1
|
|
||||||
# # java 1
|
|
||||||
# #
|
|
||||||
# puts "Global facets:", "-"*25
|
|
||||||
# s.results.facets['global-tags']['terms'].each do |f|
|
|
||||||
# puts "#{f['term'].ljust(10)} #{f['count']}"
|
|
||||||
# end
|
|
||||||
|
|
||||||
# # _ElasticSearch_ supports many advanced types of facets, such as those for computing statistics or geographical distance.
|
|
||||||
# #
|
|
||||||
# # Eventually, _Tire_ will support all of them. So far, only these are supported:
|
|
||||||
# #
|
|
||||||
# # * [terms](http://www.elasticsearch.org/guide/reference/api/search/facets/terms-facet.html)
|
|
||||||
# # * [date](http://www.elasticsearch.org/guide/reference/api/search/facets/date-histogram-facet.html)
|
|
||||||
# # * [range](http://www.elasticsearch.org/guide/reference/api/search/facets/range-facet.html)
|
|
||||||
# # * [histogram](http://www.elasticsearch.org/guide/reference/api/search/facets/histogram-facet.html)
|
|
||||||
# # * [statistical](http://www.elasticsearch.org/guide/reference/api/search/facets/statistical-facet.html)
|
|
||||||
# # * [terms_stats](http://www.elasticsearch.org/guide/reference/api/search/facets/terms-stats-facet.html)
|
|
||||||
# # * [query](http://www.elasticsearch.org/guide/reference/api/search/facets/query-facet.html)
|
|
||||||
|
|
||||||
# # We have seen that _ElasticSearch_ facets enable us to fetch complex aggregations from our data.
|
|
||||||
# #
|
|
||||||
# # They are frequently used for another feature, „faceted navigation“.
|
|
||||||
# # We can be combine query and facets with
|
|
||||||
# # [filters](http://elasticsearch.org/guide/reference/api/search/filter.html),
|
|
||||||
# # so the returned documents are restricted by certain criteria — for example to a specific category —,
|
|
||||||
# # but the aggregation calculations are still based on the original query.
|
|
||||||
|
|
||||||
|
|
||||||
# #### Filtered Search
|
|
||||||
|
|
||||||
# # So, let's make our search a bit more complex. Let's search for articles whose titles begin
|
|
||||||
# # with letter “T”, again, but filter the results, so only the articles tagged “ruby”
|
|
||||||
# # are returned.
|
|
||||||
# #
|
|
||||||
# s = Tire.search 'articles' do
|
|
||||||
|
|
||||||
# # We will use just the same **query** as before.
|
|
||||||
# #
|
|
||||||
# query { string 'title:T*' }
|
|
||||||
|
|
||||||
# # But we will add a _terms_ **filter** based on tags.
|
|
||||||
# #
|
|
||||||
# filter :terms, :tags => ['ruby']
|
|
||||||
|
|
||||||
# # And, of course, our facet definition.
|
|
||||||
# #
|
|
||||||
# facet('tags') { terms :tags }
|
|
||||||
|
|
||||||
# end
|
|
||||||
|
|
||||||
# # We see that only the article _Two_ (tagged “ruby” and “python”) is returned,
|
|
||||||
# # _not_ the article _Three_ (tagged “java”):
|
|
||||||
# #
|
|
||||||
# # * Two [tags: ruby, python]
|
|
||||||
# #
|
|
||||||
# s.results.each do |document|
|
|
||||||
# puts "* #{ document.title } [tags: ]"
|
|
||||||
# end
|
|
||||||
|
|
||||||
# # The _count_ for article _Three_'s tags, “java”, on the other hand, _is_ in fact included:
|
|
||||||
# #
|
|
||||||
# # Counts by tag:
|
|
||||||
# # -------------------------
|
|
||||||
# # ruby 1
|
|
||||||
# # python 1
|
|
||||||
# # java 1
|
|
||||||
# #
|
|
||||||
# puts "Counts by tag:", "-"*25
|
|
||||||
# s.results.facets['tags']['terms'].each do |f|
|
|
||||||
# puts "#{f['term'].ljust(10)} #{f['count']}"
|
|
||||||
# end
|
|
||||||
|
|
||||||
# #### Sorting
|
|
||||||
|
|
||||||
# # By default, the results are sorted according to their relevancy.
|
|
||||||
# #
|
|
||||||
# s = Tire.search('articles') { query { string 'tags:ruby' } }
|
|
||||||
|
|
||||||
# s.results.each do |document|
|
|
||||||
# puts "* #{ document.title } " +
|
|
||||||
# "[tags: ; " +
|
|
||||||
|
|
||||||
# # The score is available as the `_score` property.
|
|
||||||
# #
|
|
||||||
# "score: #{document._score}]"
|
|
||||||
# end
|
|
||||||
|
|
||||||
# # The results:
|
|
||||||
# #
|
|
||||||
# # * One [tags: ruby; score: 0.30685282]
|
|
||||||
# # * Four [tags: ruby, php; score: 0.19178301]
|
|
||||||
# # * Two [tags: ruby, python; score: 0.19178301]
|
|
||||||
|
|
||||||
# # But, what if we want to sort the results based on some other criteria,
|
|
||||||
# # such as published date or product price? We can do that.
|
|
||||||
# #
|
|
||||||
# s = Tire.search 'articles' do
|
|
||||||
|
|
||||||
# # We will search for articles tagged “ruby”, again, ...
|
|
||||||
# #
|
|
||||||
# query { string 'tags:ruby' }
|
|
||||||
|
|
||||||
# # ... but will sort them by their `title`, in descending order.
|
|
||||||
# #
|
|
||||||
# sort { by :title, 'desc' }
|
|
||||||
# end
|
|
||||||
|
|
||||||
# # The results:
|
|
||||||
# #
|
|
||||||
# # * Two
|
|
||||||
# # * One
|
|
||||||
# # * Four
|
|
||||||
# #
|
|
||||||
# s.results.each do |document|
|
|
||||||
# puts "* #{ document.title }"
|
|
||||||
# end
|
|
||||||
|
|
||||||
# # Of course, it's possible to combine more fields in the sorting definition.
|
|
||||||
|
|
||||||
# s = Tire.search 'articles' do
|
|
||||||
|
|
||||||
# # We will just get all articles in this case.
|
|
||||||
# #
|
|
||||||
# query { all }
|
|
||||||
|
|
||||||
# sort do
|
|
||||||
|
|
||||||
# # We will sort the results by their `published_on` property in _ascending_ order (the default),
|
|
||||||
# #
|
|
||||||
# by :published_on
|
|
||||||
|
|
||||||
# # and by their `title` property, in _descending_ order.
|
|
||||||
# #
|
|
||||||
# by :title, 'desc'
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
|
|
||||||
# # The results:
|
|
||||||
# # * One (Published on: 2011-01-01)
|
|
||||||
# # * Two (Published on: 2011-01-02)
|
|
||||||
# # * Three (Published on: 2011-01-02)
|
|
||||||
# # * Four (Published on: 2011-01-03)
|
|
||||||
# #
|
|
||||||
# s.results.each do |document|
|
|
||||||
# puts "* #{ document.title.ljust(10) } (Published on: #{ document.published_on })"
|
|
||||||
# end
|
|
||||||
|
|
||||||
# #### Highlighting
|
|
||||||
|
|
||||||
# # Often, we want to highlight the snippets matching our query in the displayed results.
|
|
||||||
# # _ElasticSearch_ provides rich
|
|
||||||
# # [highlighting](http://www.elasticsearch.org/guide/reference/api/search/highlighting.html)
|
|
||||||
# # features, and _Tire_ makes them trivial to use.
|
|
||||||
# #
|
|
||||||
# s = Tire.search 'articles' do
|
|
||||||
|
|
||||||
# # Let's search for documents containing word “Two” in their titles,
|
|
||||||
# query { string 'title:Two' }
|
|
||||||
|
|
||||||
# # and instruct _ElasticSearch_ to highlight relevant snippets.
|
|
||||||
# #
|
|
||||||
# highlight :title
|
|
||||||
# end
|
|
||||||
|
|
||||||
# # The results:
|
|
||||||
# # Title: Two; Highlighted: <em>Two</em>
|
|
||||||
# #
|
|
||||||
# s.results.each do |document|
|
|
||||||
# puts "Title: #{ document.title }; Highlighted: #{document.highlight.title}"
|
|
||||||
# end
|
|
||||||
|
|
||||||
# # We can configure many options for highlighting, such as:
|
|
||||||
# #
|
|
||||||
# s = Tire.search 'articles' do
|
|
||||||
# query { string 'title:Two' }
|
|
||||||
|
|
||||||
# # • specify the fields to highlight
|
|
||||||
# #
|
|
||||||
# highlight :title, :body
|
|
||||||
|
|
||||||
# # • specify their individual options
|
|
||||||
# #
|
|
||||||
# highlight :title, :body => { :number_of_fragments => 0 }
|
|
||||||
|
|
||||||
# # • or specify global highlighting options, such as the wrapper tag
|
|
||||||
# #
|
|
||||||
# highlight :title, :body, :options => { :tag => '<strong class="highlight">' }
|
|
||||||
# end
|
|
||||||
|
|
||||||
# #### Percolation
|
|
||||||
|
|
||||||
# # _ElasticSearch_ comes with one very interesting, and rather unique feature:
|
|
||||||
# # [_percolation_](http://www.elasticsearch.org/guide/reference/api/percolate.html).
|
|
||||||
|
|
||||||
# # It works in a „reverse search“ manner to regular search workflow of adding
|
|
||||||
# # documents to the index and then querying them.
|
|
||||||
# # Percolation allows us to register a query, and ask if a specific document
|
|
||||||
# # matches it, either on demand, or immediately as the document is being indexed.
|
|
||||||
|
|
||||||
# # Let's review an example for an index named _weather_.
|
|
||||||
# # We will register three queries for percolation against this index.
|
|
||||||
# #
|
|
||||||
# index = Tire.index('weather') do
|
|
||||||
# delete
|
|
||||||
# create
|
|
||||||
|
|
||||||
# # First, a query named _warning_,
|
|
||||||
# #
|
|
||||||
# register_percolator_query('warning', :tags => ['warning']) { string 'warning OR severe OR extreme' }
|
|
||||||
|
|
||||||
# # a query named _tsunami_,
|
|
||||||
# #
|
|
||||||
# register_percolator_query('tsunami', :tags => ['tsunami']) { string 'tsunami' }
|
|
||||||
|
|
||||||
# # and a query named _floods_.
|
|
||||||
# #
|
|
||||||
# register_percolator_query('floods', :tags => ['floods']) { string 'flood*' }
|
|
||||||
|
|
||||||
# end
|
|
||||||
|
|
||||||
# # Notice, that we have added a _tags_ field to the query document, because it behaves
|
|
||||||
# # just like any other document in _ElasticSearch_.
|
|
||||||
|
|
||||||
# # We will refresh the `_percolator` index for immediate access.
|
|
||||||
# #
|
|
||||||
# Tire.index('_percolator').refresh
|
|
||||||
|
|
||||||
# # Now, let's _percolate_ a document containing some trigger words against all registered queries.
|
|
||||||
# #
|
|
||||||
# matches = index.percolate(:message => '[Warning] Extreme flooding expected after tsunami wave.')
|
|
||||||
|
|
||||||
# # The result will contain, unsurprisingly, names of all the three registered queries:
|
|
||||||
# #
|
|
||||||
# # Matching queries: ["floods", "tsunami", "warning"]
|
|
||||||
# #
|
|
||||||
# puts "Matching queries: " + matches.inspect
|
|
||||||
|
|
||||||
# # We can filter the executed queries with a regular _ElasticSearch_ query passed as a block to
|
|
||||||
# # the `percolate` method.
|
|
||||||
# #
|
|
||||||
# matches = index.percolate(:message => '[Warning] Extreme flooding expected after tsunami wave.') do
|
|
||||||
# # Let's use a _terms_ query against the `tags` field.
|
|
||||||
# term :tags, 'tsunami'
|
|
||||||
# end
|
|
||||||
|
|
||||||
# # In this case, the result will contain only the name of the “tsunami” query.
|
|
||||||
# #
|
|
||||||
# # Matching queries: ["tsunami"]
|
|
||||||
# #
|
|
||||||
# puts "Matching queries: " + matches.inspect
|
|
||||||
|
|
||||||
# # What if we percolate another document, without the “tsunami” trigger word?
|
|
||||||
# #
|
|
||||||
# matches = index.percolate(:message => '[Warning] Extreme temperatures expected.') { term :tags, 'tsunami' }
|
|
||||||
|
|
||||||
# # As expected, we will get an empty array:
|
|
||||||
# #
|
|
||||||
# # Matching queries: []
|
|
||||||
# #
|
|
||||||
# puts "Matching queries: " + matches.inspect
|
|
||||||
|
|
||||||
# # Well, that's of course immensely useful for real-time search systems. But, there's more.
|
|
||||||
# # We can _percolate_ a document _at the same time_ it is being stored in the index,
|
|
||||||
# # getting back a list of matching queries.
|
|
||||||
|
|
||||||
# # Let's store a document with some trigger words in the index, and mark it for percolation.
|
|
||||||
# #
|
|
||||||
# response = index.store :message => '[Warning] Severe floods expected after tsunami wave.', :percolate => true
|
|
||||||
|
|
||||||
# # We will get the names of all matching queries in response.
|
|
||||||
# #
|
|
||||||
# # Matching queries: ["floods", "tsunami", "warning"]
|
|
||||||
# #
|
|
||||||
# puts "Matching queries: " + response['matches'].inspect
|
|
||||||
|
|
||||||
# # As with the _percolate_ example, we can filter the executed queries.
|
|
||||||
# #
|
|
||||||
# response = index.store :message => '[Warning] Severe floods expected after tsunami wave.',
|
|
||||||
# # Let's use a simple string query for the “tsunami” tag.
|
|
||||||
# :percolate => 'tags:tsunami'
|
|
||||||
|
|
||||||
# # Unsurprisingly, the response will contain just the name of the “tsunami” query.
|
|
||||||
# #
|
|
||||||
# # Matching queries: ["tsunami"]
|
|
||||||
# #
|
|
||||||
# puts "Matching queries: " + response['matches'].inspect
|
|
||||||
|
|
||||||
# ### ActiveModel Integration
|
|
||||||
|
|
||||||
# # As you can see, [_Tire_](https://github.com/karmi/tire) supports the
|
|
||||||
# # main features of _ElasticSearch_ in Ruby.
|
|
||||||
# #
|
|
||||||
# # It allows you to create and delete indices, add documents, search them, retrieve the facets, highlight the results,
|
|
||||||
# # and comes with a usable logging facility.
|
|
||||||
# #
|
|
||||||
# # Of course, the holy grail of any search library is easy, painless integration with your Ruby classes, and,
|
|
||||||
# # most importantly, with ActiveRecord/ActiveModel classes.
|
|
||||||
# #
|
|
||||||
# # Please, check out the [README](https://github.com/karmi/tire/tree/master#readme) file for instructions
|
|
||||||
# # how to include _Tire_-based search in your models..
|
|
||||||
# #
|
|
||||||
# # Send any feedback via Github issues, or ask questions in the [#elasticsearch](irc://irc.freenode.net/#elasticsearch) IRC channel.
|
|
|
@ -9,17 +9,24 @@ class Panel::Announcement::FrontEnd::BulletinsController < OrbitWidgetController
|
||||||
# GET /bulletins.xml
|
# GET /bulletins.xml
|
||||||
|
|
||||||
def index
|
def index
|
||||||
|
if !params[:search_query].blank?
|
||||||
date_now = Time.now
|
search_cond = {:is_checked=>true,:is_hidden=>false}
|
||||||
if !params[:category_id].blank?
|
search_cond.merge!({:bulletin_category_id => "#{params[:category_id]}" }) if !params[:category_id].blank?
|
||||||
@bulletins = Bulletin.all.can_display.where(:bulletin_category_id => params[:category_id]).any_of( {deadline: nil,:postdate.lte => date_now} , {:deadline.gte => date_now,:postdate.lte => date_now} ).desc( :is_top, :postdate).page( params[:page_main]).per(10)
|
search = Redis::Search.query("Bulletin", params[:search_query], :conditions =>search_cond)
|
||||||
@current_category = BulletinCategory.from_id(params[:category_id]) rescue nil
|
search_result = search.collect{|t| t["id"]}
|
||||||
elsif !params[:tag_id].blank?
|
@news_bulletins = Bulletin.all.can_display.any_in(_id:search_result).page( params[:page_main]).per(10)
|
||||||
@tag = AnnouncementTag.find(params[:tag_id]) rescue nil
|
|
||||||
@tag = AnnouncementTag.where(key: params[:tag_id])[0] unless @tag
|
|
||||||
@bulletins = @tag.bulletins.can_display.any_of( {deadline: nil,:postdate.lte => date_now} , {:deadline.gte => date_now,:postdate.lte => date_now} ).desc( :is_top, :postdate).page( params[:page_main]).per(10)
|
|
||||||
else
|
else
|
||||||
@bulletins = Bulletin.all.can_display.any_of( {deadline: nil,:postdate.lte => date_now} , {:deadline.gte => date_now,:postdate.lte => date_now} ).desc( :is_top, :postdate).page( params[:page_main]).per(10)
|
date_now = Time.now
|
||||||
|
if !params[:category_id].blank?
|
||||||
|
@bulletins = Bulletin.all.can_display.where(:bulletin_category_id => params[:category_id]).any_of( {deadline: nil,:postdate.lte => date_now} , {:deadline.gte => date_now,:postdate.lte => date_now} ).desc( :is_top, :postdate).page( params[:page_main]).per(10)
|
||||||
|
@current_category = BulletinCategory.from_id(params[:category_id]) rescue nil
|
||||||
|
elsif !params[:tag_id].blank?
|
||||||
|
@tag = AnnouncementTag.find(params[:tag_id]) rescue nil
|
||||||
|
@tag = AnnouncementTag.where(key: params[:tag_id])[0] unless @tag
|
||||||
|
@bulletins = @tag.bulletins.can_display.any_of( {deadline: nil,:postdate.lte => date_now} , {:deadline.gte => date_now,:postdate.lte => date_now} ).desc( :is_top, :postdate).page( params[:page_main]).per(10)
|
||||||
|
else
|
||||||
|
@bulletins = Bulletin.all.can_display.any_of( {deadline: nil,:postdate.lte => date_now} , {:deadline.gte => date_now,:postdate.lte => date_now} ).desc( :is_top, :postdate).page( params[:page_main]).per(10)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -47,6 +47,9 @@ class Panel::Announcement::Widget::BulletinsController < OrbitWidgetController
|
||||||
get_categorys
|
get_categorys
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def bulletins_search_block
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
# encoding: utf-8
|
# encoding: utf-8
|
||||||
|
# require "impressionist"
|
||||||
class Bulletin
|
class Bulletin
|
||||||
include Mongoid::Document
|
include Mongoid::Document
|
||||||
include Mongoid::Timestamps
|
include Mongoid::Timestamps
|
||||||
include Mongoid::MultiParameterAttributes
|
include Mongoid::MultiParameterAttributes
|
||||||
|
include Redis::Search
|
||||||
include Impressionist::Impressionable
|
include Impressionist::Impressionable
|
||||||
|
|
||||||
BelongsToCategory = :bulletin_category
|
BelongsToCategory = :bulletin_category
|
||||||
|
@ -13,7 +14,7 @@ class Bulletin
|
||||||
|
|
||||||
scope :searchable,where(:is_checked=>true,:is_hidden=>false,:is_pending=>false)
|
scope :searchable,where(:is_checked=>true,:is_hidden=>false,:is_pending=>false)
|
||||||
|
|
||||||
is_impressionable :counter_cache => { :column_name => :view_count }
|
# is_impressionable :counter_cache => { :column_name => :view_count }
|
||||||
|
|
||||||
has_one :title, :class_name => "I18nVariable", :as => :language_value, :autosave => true, :dependent => :destroy
|
has_one :title, :class_name => "I18nVariable", :as => :language_value, :autosave => true, :dependent => :destroy
|
||||||
has_one :subtitle, :class_name => "I18nVariable", :as => :language_value, :autosave => true, :dependent => :destroy
|
has_one :subtitle, :class_name => "I18nVariable", :as => :language_value, :autosave => true, :dependent => :destroy
|
||||||
|
@ -62,7 +63,29 @@ class Bulletin
|
||||||
after_save :save_bulletin_links
|
after_save :save_bulletin_links
|
||||||
after_save :save_bulletin_files
|
after_save :save_bulletin_files
|
||||||
|
|
||||||
|
redis_search_index(:title_field => :s_title,
|
||||||
|
:alias_field =>:s_title_en ,
|
||||||
|
:score_field => :view_count,
|
||||||
|
:condition_fields => [:is_checked,:is_hidden,:bulletin_category_id],
|
||||||
|
:ext_fields => [])
|
||||||
|
# def gen_title_for_search
|
||||||
|
# # [s_title,s_title_en,s_text_en,s_text_zh_tw].join(' ')
|
||||||
|
# end
|
||||||
|
|
||||||
|
def s_title
|
||||||
|
self.title.zh_tw
|
||||||
|
end
|
||||||
|
|
||||||
|
def s_title_en
|
||||||
|
self.title.en
|
||||||
|
end
|
||||||
|
|
||||||
|
# def s_text_en
|
||||||
|
# Nokogiri::HTML(self.text.en).text
|
||||||
|
# end
|
||||||
|
# def s_text_zh_tw
|
||||||
|
# Nokogiri::HTML(self.text.zh_tw).text
|
||||||
|
# end
|
||||||
|
|
||||||
# scope :currently_available, lambda { |category, limit|
|
# scope :currently_available, lambda { |category, limit|
|
||||||
# # limit ||= 5
|
# # limit ||= 5
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
<%= form_tag panel_announcement_front_end_bulletins_path, method: :get do %>
|
||||||
|
<p>
|
||||||
|
<%= text_field_tag :search_query, params[:search_query] %>
|
||||||
|
<%= submit_tag "Search", name: nil %>
|
||||||
|
</p>
|
||||||
|
<% end %>
|
|
@ -44,6 +44,7 @@ Rails.application.routes.draw do
|
||||||
match "reload_bulletins" => "bulletins#reload_bulletins"
|
match "reload_bulletins" => "bulletins#reload_bulletins"
|
||||||
match "reload_web_links" => "bulletins#reload_web_links"
|
match "reload_web_links" => "bulletins#reload_web_links"
|
||||||
match "bulletins_side_bar" => "bulletins#bulletins_side_bar"
|
match "bulletins_side_bar" => "bulletins#bulletins_side_bar"
|
||||||
|
match "bulletins_search_block" => "bulletins#bulletins_search_block"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,21 +10,10 @@ class Panel::News::FrontEnd::NewsBulletinsController < OrbitWidgetController
|
||||||
|
|
||||||
def index
|
def index
|
||||||
if !params[:search_query].blank?
|
if !params[:search_query].blank?
|
||||||
search_query =params[:search_query].gsub(/"/,"").split(" ")
|
search_cond = {:is_checked=>true,:is_hidden=>false}
|
||||||
|
search_cond.merge!({:news_bulletin_category_id => "#{params[:category_id]}" }) if !params[:category_id].blank?
|
||||||
words_query = lambda do |boolean|
|
search = Redis::Search.query("NewsBulletin", params[:search_query], :conditions =>search_cond)
|
||||||
search_query.each do |word|
|
search_result = search.collect{|t| t["id"]}
|
||||||
boolean.should { string "title:#{word}" }
|
|
||||||
#boolean.should { string "sub_title:#{word}" }
|
|
||||||
#boolean.should { string "text:#{word}" }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
search_result=Tire.search('news_bulletins') do
|
|
||||||
query {boolean &words_query }
|
|
||||||
#raise to_curl
|
|
||||||
end.results.collect{|t| t.id}
|
|
||||||
|
|
||||||
@news_bulletins = NewsBulletin.all.can_display.any_in(_id:search_result).page( params[:page_main]).per(10)
|
@news_bulletins = NewsBulletin.all.can_display.any_in(_id:search_result).page( params[:page_main]).per(10)
|
||||||
else
|
else
|
||||||
date_now = Time.now
|
date_now = Time.now
|
||||||
|
@ -50,7 +39,7 @@ class Panel::News::FrontEnd::NewsBulletinsController < OrbitWidgetController
|
||||||
else
|
else
|
||||||
@news_bulletin = NewsBulletin.all.can_display.where(_id: params[:id]).first
|
@news_bulletin = NewsBulletin.all.can_display.where(_id: params[:id]).first
|
||||||
unless @news_bulletin.disable?
|
unless @news_bulletin.disable?
|
||||||
impressionist(@news_bulletin)
|
# impressionist(@news_bulletin)
|
||||||
get_categorys
|
get_categorys
|
||||||
else
|
else
|
||||||
render :nothing => true, :status => 403
|
render :nothing => true, :status => 403
|
||||||
|
|
|
@ -47,8 +47,9 @@ caches_page :home_banner
|
||||||
get_categorys
|
get_categorys
|
||||||
end
|
end
|
||||||
|
|
||||||
# def news_bulletins_search_block
|
def news_bulletins_search_block
|
||||||
# end
|
|
||||||
|
end
|
||||||
|
|
||||||
def home_banner
|
def home_banner
|
||||||
if !params[:category_id].blank?
|
if !params[:category_id].blank?
|
||||||
|
|
|
@ -4,6 +4,7 @@ class NewsBulletin
|
||||||
include Mongoid::Document
|
include Mongoid::Document
|
||||||
include Mongoid::Timestamps
|
include Mongoid::Timestamps
|
||||||
include Mongoid::MultiParameterAttributes
|
include Mongoid::MultiParameterAttributes
|
||||||
|
include Redis::Search
|
||||||
include Impressionist::Impressionable
|
include Impressionist::Impressionable
|
||||||
|
|
||||||
BelongsToCategory = :news_bulletin_category
|
BelongsToCategory = :news_bulletin_category
|
||||||
|
@ -16,7 +17,7 @@ class NewsBulletin
|
||||||
|
|
||||||
scope :searchable,where(:is_checked=>true,:is_hidden=>false,:is_pending=>false)
|
scope :searchable,where(:is_checked=>true,:is_hidden=>false,:is_pending=>false)
|
||||||
|
|
||||||
is_impressionable :counter_cache => { :column_name => :view_count }
|
# is_impressionable :counter_cache => { :column_name => :view_count }
|
||||||
|
|
||||||
has_one :title, :class_name => "I18nVariable", :as => :language_value, :autosave => true, :dependent => :destroy
|
has_one :title, :class_name => "I18nVariable", :as => :language_value, :autosave => true, :dependent => :destroy
|
||||||
has_one :subtitle, :class_name => "I18nVariable", :as => :language_value, :autosave => true, :dependent => :destroy
|
has_one :subtitle, :class_name => "I18nVariable", :as => :language_value, :autosave => true, :dependent => :destroy
|
||||||
|
@ -89,32 +90,29 @@ class NewsBulletin
|
||||||
"news_bulletin"
|
"news_bulletin"
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_indexed_json
|
redis_search_index(:title_field => :s_title,
|
||||||
titles = title.zh_tw #+ title.en
|
:alias_field => :s_title_en,
|
||||||
sub_titles = subtitle.zh_tw #+ subtitle.en
|
:score_field => :view_count,
|
||||||
texts = text.zh_tw #+ text.en
|
:condition_fields => [:is_checked,:is_hidden,:news_bulletin_category_id],
|
||||||
{
|
:ext_fields =>[])
|
||||||
:_id => _id,
|
# def gen_title_for_search
|
||||||
:title => Nokogiri::HTML(titles).text,
|
# # [s_title,s_title_en,s_text_en,s_text_zh_tw].join(' ')
|
||||||
:sub_title => Nokogiri::HTML(sub_titles).text,
|
# end
|
||||||
:text => Nokogiri::HTML(texts).text
|
|
||||||
}.to_json
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.search( search = nil, category_id = nil )
|
def s_title
|
||||||
if category_id.to_s.size > 0 and search.to_s.size > 0
|
#self.title.zh_tw
|
||||||
key = /#{search}/
|
end
|
||||||
find(:all, :conditions => {title: key, news_bulletin_category_id: category_id}).desc( :is_top, :postdate )
|
|
||||||
elsif category_id.to_s.size > 0 and search.to_s.size < 1
|
|
||||||
find(:all, :conditions => {news_bulletin_category_id: category_id}).desc( :is_top, :postdate )
|
|
||||||
elsif search.to_s.size > 0 and category_id.to_s.size < 1
|
|
||||||
key = /#{search}/
|
|
||||||
find(:all, :conditions => {title: key}).desc( :is_top, :postdate )
|
|
||||||
else
|
|
||||||
find(:all).desc( :is_top, :postdate)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
def s_title_en
|
||||||
|
#self.title.en
|
||||||
|
end
|
||||||
|
|
||||||
|
# def s_text_en
|
||||||
|
# Nokogiri::HTML(self.text.en).text
|
||||||
|
# end
|
||||||
|
# def s_text_zh_tw
|
||||||
|
# Nokogiri::HTML(self.text.zh_tw).text
|
||||||
|
# end
|
||||||
|
|
||||||
def self.widget_datas( category_id = nil )
|
def self.widget_datas( category_id = nil )
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<%= form_tag panel_news_front_end_news_bulletins_path, method: :get do %>
|
<%= form_tag panel_news_front_end_news_bulletins_path, method: :get do %>
|
||||||
<p>
|
<p>
|
||||||
<%= text_field_tag :search_query, params[:search_query] %>
|
<%= text_field_tag :search_query, params[:search_query] %>
|
||||||
|
<%= hidden_field_tag :category_id, params[:category_id] %>
|
||||||
<%= submit_tag "Search", name: nil %>
|
<%= submit_tag "Search", name: nil %>
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
|
@ -7,7 +7,7 @@ platforms :jruby do
|
||||||
end
|
end
|
||||||
|
|
||||||
platforms :ruby, :mswin, :mingw do
|
platforms :ruby, :mswin, :mingw do
|
||||||
# gem 'sqlite3'
|
gem 'sqlite3'
|
||||||
end
|
end
|
||||||
|
|
||||||
gemspec
|
gemspec
|
||||||
|
|
|
@ -29,22 +29,17 @@ module Impressionist
|
||||||
end
|
end
|
||||||
|
|
||||||
def impressionist_count(options={})
|
def impressionist_count(options={})
|
||||||
# options.reverse_merge!(:filter=>:request_hash, :start_date=>nil, :end_date=>Time.now)
|
options.reverse_merge!(:filter=>:request_hash, :start_date=>nil, :end_date=>Time.now)
|
||||||
# imps = options[:start_date].blank? ? impressions : impressions.where("created_at>=? and created_at<=?",options[:start_date],options[:end_date])
|
imps = options[:start_date].blank? ? impressions : impressions.where("created_at>=? and created_at<=?",options[:start_date],options[:end_date])
|
||||||
# options[:filter] == :all ? imps.count : imps.count(options[:filter], :distinct => true)
|
options[:filter] == :all ? imps.count : imps.count(options[:filter], :distinct => true)
|
||||||
options.reverse_merge!(:filter => :request_hash, :start_date => nil, :end_date => Time.now)
|
|
||||||
imps = options[:start_date].blank? ? impressions : impressions.where(:created_at.gte => options[:start_date], :created_at.lte => options[:end_date])
|
|
||||||
options[:filter] == :all ? imps.count : imps.distinct(options[:filter]).count
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_impressionist_counter_cache
|
def update_impressionist_counter_cache
|
||||||
# cache_options = self.class.impressionist_counter_cache_options
|
|
||||||
# column_name = cache_options[:column_name].to_sym
|
|
||||||
# count = cache_options[:unique] ? impressionist_count(:filter => :ip_address) : impressionist_count
|
|
||||||
# update_attribute(column_name, count)
|
|
||||||
cache_options = self.class.impressionist_counter_cache_options
|
cache_options = self.class.impressionist_counter_cache_options
|
||||||
column_name = cache_options[:column_name].to_sym
|
column_name = cache_options[:column_name].to_sym
|
||||||
update_attribute(column_name, self.send(cache_options[:column_name]) + 1)
|
count = cache_options[:unique] ? impressionist_count(:filter => :ip_address) : impressionist_count
|
||||||
|
old_count = send(column_name) || 0
|
||||||
|
self.class.update_counters(id, column_name => (count - old_count))
|
||||||
end
|
end
|
||||||
|
|
||||||
# OLD METHODS - DEPRECATE IN V0.5
|
# OLD METHODS - DEPRECATE IN V0.5
|
||||||
|
|
|
@ -7,6 +7,7 @@ module Impressionist
|
||||||
def copy_config_file
|
def copy_config_file
|
||||||
template 'impression.rb', 'config/initializers/impression.rb'
|
template 'impression.rb', 'config/initializers/impression.rb'
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# Use this hook to configure impressionist parameters
|
# Use this hook to configure impressionist parameters
|
||||||
Impressionist.setup do |config|
|
Impressionist.setup do |config|
|
||||||
# Define ORM. Could be :active_record (default) and :mongo_mapper
|
# Define ORM. Could be :active_record (default), :mongo_mapper or :mongoid
|
||||||
# config.orm = :active_record
|
# config.orm = :active_record
|
||||||
end
|
end
|
||||||
|
|
10
vendor/impressionist/lib/impressionist/controllers/mongoid/impressionist_controller.rb
vendored
Normal file
10
vendor/impressionist/lib/impressionist/controllers/mongoid/impressionist_controller.rb
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
ImpressionistController::InstanceMethods.send(:define_method, :direct_create_statement) do |query_params={}|
|
||||||
|
# creates a statment hash that contains default values for creating an impression.
|
||||||
|
# if :impressionable_id is a valid ObjectId then convert it into one
|
||||||
|
base = (defined? Moped) ? Moped::BSON : BSON
|
||||||
|
query_params.reverse_merge!(
|
||||||
|
:impressionable_type => controller_name.singularize.camelize,
|
||||||
|
:impressionable_id=> !base::ObjectId.legal?(params[:id]) ? params[:id] : base::ObjectId.from_string(params[:id])
|
||||||
|
)
|
||||||
|
associative_create_statement(query_params)
|
||||||
|
end
|
|
@ -5,7 +5,7 @@ module Impressionist
|
||||||
class Engine < Rails::Engine
|
class Engine < Rails::Engine
|
||||||
initializer 'impressionist.model' do |app|
|
initializer 'impressionist.model' do |app|
|
||||||
require "#{root}/app/models/impressionist/impressionable.rb"
|
require "#{root}/app/models/impressionist/impressionable.rb"
|
||||||
if Impressionist.orm == :active_record
|
if Impressionist.orm == :active_record && defined? ActiveRecord
|
||||||
require "impressionist/models/active_record/impression.rb"
|
require "impressionist/models/active_record/impression.rb"
|
||||||
require "impressionist/models/active_record/impressionist/impressionable.rb"
|
require "impressionist/models/active_record/impressionist/impressionable.rb"
|
||||||
ActiveRecord::Base.send(:include, Impressionist::Impressionable)
|
ActiveRecord::Base.send(:include, Impressionist::Impressionable)
|
||||||
|
@ -14,13 +14,16 @@ module Impressionist
|
||||||
require "impressionist/models/mongo_mapper/impressionist/impressionable.rb"
|
require "impressionist/models/mongo_mapper/impressionist/impressionable.rb"
|
||||||
MongoMapper::Document.plugin Impressionist::Impressionable
|
MongoMapper::Document.plugin Impressionist::Impressionable
|
||||||
elsif Impressionist.orm == :mongoid
|
elsif Impressionist.orm == :mongoid
|
||||||
require "impressionist/models/mongoid/impression.rb"
|
require 'impressionist/models/mongoid/impression.rb'
|
||||||
require "impressionist/models/mongoid/impressionist/impressionable.rb"
|
require 'impressionist/models/mongoid/impressionist/impressionable.rb'
|
||||||
# Mongoid::Document Impressionist::Impressionable
|
Mongoid::Document.send(:include, Impressionist::Impressionable)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
initializer 'impressionist.controller' do
|
initializer 'impressionist.controller' do
|
||||||
|
if Impressionist.orm == :mongoid
|
||||||
|
require 'impressionist/controllers/mongoid/impressionist_controller.rb'
|
||||||
|
end
|
||||||
ActiveSupport.on_load(:action_controller) do
|
ActiveSupport.on_load(:action_controller) do
|
||||||
include ImpressionistController::InstanceMethods
|
include ImpressionistController::InstanceMethods
|
||||||
extend ImpressionistController::ClassMethods
|
extend ImpressionistController::ClassMethods
|
||||||
|
|
|
@ -2,8 +2,12 @@ class Impression
|
||||||
include Mongoid::Document
|
include Mongoid::Document
|
||||||
include Mongoid::Timestamps
|
include Mongoid::Timestamps
|
||||||
|
|
||||||
field :impressionable_type
|
attr_accessible :impressionable_type, :impressionable_field, :impressionable_id, :user_id,
|
||||||
field :impressionable_id
|
:controller_name, :action_name, :view_name, :request_hash, :ip_address,
|
||||||
|
:session_hash, :message, :referrer
|
||||||
|
|
||||||
|
belongs_to :impressionable, polymorphic: true
|
||||||
|
|
||||||
field :user_id
|
field :user_id
|
||||||
field :controller_name
|
field :controller_name
|
||||||
field :action_name
|
field :action_name
|
||||||
|
@ -14,26 +18,15 @@ class Impression
|
||||||
field :message
|
field :message
|
||||||
field :referrer
|
field :referrer
|
||||||
|
|
||||||
belongs_to :impressionable, :polymorphic => true
|
set_callback(:create, :after) do |doc|
|
||||||
|
unless impressionable_id.nil?
|
||||||
after_save :update_impressions_counter_cache
|
impressionable_class = doc.impressionable_type.constantize
|
||||||
|
|
||||||
def self.impressionist_count(options={})
|
|
||||||
options.reverse_merge!(:filter => :request_hash, :start_date => nil, :end_date => Time.now)
|
|
||||||
imps = options[:start_date].blank? ? impressions : impressions.where(:created_at.gte => options[:start_date], :created_at.lte => options[:end_date])
|
|
||||||
options[:filter] == :all ? imps.count : imps.distinct(options[:filter]).count
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def update_impressions_counter_cache
|
|
||||||
if self.referrer
|
|
||||||
impressionable_class = self.impressionable_type.constantize
|
|
||||||
|
|
||||||
if impressionable_class.impressionist_counter_cache_options
|
if impressionable_class.impressionist_counter_cache_options
|
||||||
resouce = impressionable_class.find(self.impressionable_id)
|
resource = impressionable_class.find(doc.impressionable_id)
|
||||||
resouce.try(:update_impressionist_counter_cache)
|
resource.try(:update_impressionist_counter_cache)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,9 +4,49 @@ module Impressionist
|
||||||
|
|
||||||
module ClassMethods
|
module ClassMethods
|
||||||
def is_impressionable(options={})
|
def is_impressionable(options={})
|
||||||
has_many :impressions, :as => :impressionable, :dependent => :destroy
|
has_many :impressions, as: :impressionable, dependent: :destroy
|
||||||
@impressionist_cache_options = options[:counter_cache]
|
@impressionist_cache_options = options[:counter_cache]
|
||||||
|
if !@impressionist_cache_options.nil?
|
||||||
|
opts = impressionist_counter_cache_options
|
||||||
|
field opts[:column_name], type: Integer
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def impressionist_counter_cache_options
|
||||||
|
if @impressionist_cache_options
|
||||||
|
options = { :column_name => :impressions_count, :unique => false }
|
||||||
|
options.merge!(@impressionist_cache_options) if @impressionist_cache_options.is_a?(Hash)
|
||||||
|
options
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def impressionist_counter_caching?
|
||||||
|
impressionist_counter_cache_options.present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def counter_caching?
|
||||||
|
::ActiveSupport::Deprecation.warn("#counter_caching? is deprecated; please use #impressionist_counter_caching? instead")
|
||||||
|
impressionist_counter_caching?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def impressionable?
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def impressionist_count(options={})
|
||||||
|
options.reverse_merge!(:filter=>:request_hash, :start_date=>nil, :end_date=>Time.now)
|
||||||
|
imps = options[:start_date].blank? ? impressions : impressions.between(created_at: options[:start_date]..options[:end_date])
|
||||||
|
options[:filter] == :all ? imps.count : imps.where(options[:filter].ne => nil).count
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_impressionist_counter_cache
|
||||||
|
cache_options = self.class.impressionist_counter_cache_options
|
||||||
|
column_name = cache_options[:column_name].to_sym
|
||||||
|
count = cache_options[:unique] ? impressionist_count(:filter => :ip_address) : impressionist_count
|
||||||
|
old_count = send(column_name) || 0
|
||||||
|
self.inc(column_name, (count - old_count))
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
|
@ -38,6 +38,13 @@ describe Impression do
|
||||||
@widget.reload
|
@widget.reload
|
||||||
}.should change(@widget, :impressions_count).from(0).to(1)
|
}.should change(@widget, :impressions_count).from(0).to(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "should not update the timestamp on the impressable" do
|
||||||
|
lambda {
|
||||||
|
@widget.impressions.create(:request_hash => 'abcd1234')
|
||||||
|
@widget.reload
|
||||||
|
}.should_not change(@widget, :updated_at)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
README.rdoc
|
|
||||||
lib/**/*.rb
|
|
||||||
bin/*
|
|
||||||
features/**/*.feature
|
|
||||||
LICENSE
|
|
|
@ -1,20 +0,0 @@
|
||||||
Copyright (c) 2009 jugyo
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
a copy of this software and associated documentation files (the
|
|
||||||
"Software"), to deal in the Software without restriction, including
|
|
||||||
without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be
|
|
||||||
included in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
@ -1,53 +0,0 @@
|
||||||
sunspot_mongoid
|
|
||||||
====
|
|
||||||
|
|
||||||
A Sunspot wrapper for Mongoid.
|
|
||||||
|
|
||||||
Install
|
|
||||||
----
|
|
||||||
|
|
||||||
gem install sunspot_mongoid
|
|
||||||
|
|
||||||
Examples
|
|
||||||
----
|
|
||||||
|
|
||||||
class Post
|
|
||||||
include Mongoid::Document
|
|
||||||
field :title
|
|
||||||
|
|
||||||
include Sunspot::Mongoid
|
|
||||||
searchable do
|
|
||||||
text :title
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
For Rails3
|
|
||||||
----
|
|
||||||
|
|
||||||
### as gem:
|
|
||||||
|
|
||||||
add a gem to Gemfile as following,
|
|
||||||
|
|
||||||
gem 'sunspot_mongoid'
|
|
||||||
|
|
||||||
### as plugin:
|
|
||||||
|
|
||||||
add gems to Gemfile as following,
|
|
||||||
|
|
||||||
gem 'sunspot'
|
|
||||||
gem 'sunspot_rails'
|
|
||||||
|
|
||||||
and install sunspot_mongoid as rails plugin,
|
|
||||||
|
|
||||||
rails plugin install git://github.com/jugyo/sunspot_mongoid.git
|
|
||||||
|
|
||||||
Links
|
|
||||||
----
|
|
||||||
|
|
||||||
* [sunspot](http://github.com/outoftime/sunspot)
|
|
||||||
* [sunspot_rails](http://github.com/outoftime/sunspot/tree/master/sunspot_rails/)
|
|
||||||
|
|
||||||
Copyright
|
|
||||||
----
|
|
||||||
|
|
||||||
Copyright (c) 2010 jugyo. See LICENSE for details.
|
|
|
@ -1,57 +0,0 @@
|
||||||
require 'rubygems'
|
|
||||||
require 'rake'
|
|
||||||
|
|
||||||
begin
|
|
||||||
require 'jeweler'
|
|
||||||
Jeweler::Tasks.new do |gem|
|
|
||||||
gem.name = "sunspot_mongoid"
|
|
||||||
gem.summary = %Q{A Sunspot wrapper for Mongoid.}
|
|
||||||
gem.description = %Q{A Sunspot wrapper for Mongoid that is like sunspot_rails.}
|
|
||||||
gem.email = "jugyo.org@gmail.com"
|
|
||||||
gem.homepage = "http://github.com/jugyo/sunspot_mongoid"
|
|
||||||
gem.authors = ["jugyo"]
|
|
||||||
gem.add_runtime_dependency "mongoid", ">= 0"
|
|
||||||
gem.add_runtime_dependency "sunspot", ">= 1.1.0"
|
|
||||||
gem.add_runtime_dependency "sunspot_rails", ">= 1.1.0"
|
|
||||||
gem.add_development_dependency "shoulda", ">= 0"
|
|
||||||
gem.add_development_dependency "rr", ">= 0"
|
|
||||||
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
|
||||||
end
|
|
||||||
Jeweler::GemcutterTasks.new
|
|
||||||
rescue LoadError
|
|
||||||
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
|
||||||
end
|
|
||||||
|
|
||||||
require 'rake/testtask'
|
|
||||||
Rake::TestTask.new(:test) do |test|
|
|
||||||
test.libs << 'lib' << 'test'
|
|
||||||
test.pattern = 'test/**/test_*.rb'
|
|
||||||
test.verbose = true
|
|
||||||
end
|
|
||||||
|
|
||||||
begin
|
|
||||||
require 'rcov/rcovtask'
|
|
||||||
Rcov::RcovTask.new do |test|
|
|
||||||
test.libs << 'test'
|
|
||||||
test.pattern = 'test/**/test_*.rb'
|
|
||||||
test.verbose = true
|
|
||||||
end
|
|
||||||
rescue LoadError
|
|
||||||
task :rcov do
|
|
||||||
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
task :test => :check_dependencies
|
|
||||||
|
|
||||||
task :default => :test
|
|
||||||
|
|
||||||
require 'rake/rdoctask'
|
|
||||||
Rake::RDocTask.new do |rdoc|
|
|
||||||
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
|
||||||
|
|
||||||
rdoc.rdoc_dir = 'rdoc'
|
|
||||||
rdoc.title = "sunspot_mongoid #{version}"
|
|
||||||
rdoc.rdoc_files.include('README*')
|
|
||||||
rdoc.rdoc_files.include('lib/**/*.rb')
|
|
||||||
end
|
|
|
@ -1 +0,0 @@
|
||||||
0.4.1
|
|
|
@ -1,36 +0,0 @@
|
||||||
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
|
||||||
require 'sunspot_mongoid'
|
|
||||||
|
|
||||||
Mongoid.configure do |config|
|
|
||||||
config.master = Mongo::Connection.new.db('sunspot-mongoid-test')
|
|
||||||
end
|
|
||||||
|
|
||||||
# model
|
|
||||||
class Post
|
|
||||||
include Mongoid::Document
|
|
||||||
field :title
|
|
||||||
|
|
||||||
include Sunspot::Mongoid
|
|
||||||
searchable do
|
|
||||||
text :title
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# remove old indexes
|
|
||||||
Post.destroy_all
|
|
||||||
|
|
||||||
# indexing
|
|
||||||
Post.create(:title => 'foo')
|
|
||||||
Post.create(:title => 'foo bar')
|
|
||||||
Post.create(:title => 'bar baz')
|
|
||||||
|
|
||||||
# commit
|
|
||||||
Sunspot.commit
|
|
||||||
|
|
||||||
# search
|
|
||||||
search = Post.search do
|
|
||||||
keywords 'foo'
|
|
||||||
end
|
|
||||||
search.each_hit_with_result do |hit, post|
|
|
||||||
p post
|
|
||||||
end
|
|
|
@ -1 +0,0 @@
|
||||||
require 'sunspot_mongoid'
|
|
|
@ -1,49 +0,0 @@
|
||||||
require 'sunspot'
|
|
||||||
require 'mongoid'
|
|
||||||
require 'sunspot/rails'
|
|
||||||
|
|
||||||
# == Examples:
|
|
||||||
#
|
|
||||||
# class Post
|
|
||||||
# include Mongoid::Document
|
|
||||||
# field :title
|
|
||||||
#
|
|
||||||
# include Sunspot::Mongoid
|
|
||||||
# searchable do
|
|
||||||
# text :title
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
module Sunspot
|
|
||||||
module Mongoid
|
|
||||||
def self.included(base)
|
|
||||||
base.class_eval do
|
|
||||||
extend Sunspot::Rails::Searchable::ActsAsMethods
|
|
||||||
Sunspot::Adapters::DataAccessor.register(DataAccessor, base)
|
|
||||||
Sunspot::Adapters::InstanceAdapter.register(InstanceAdapter, base)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class InstanceAdapter < Sunspot::Adapters::InstanceAdapter
|
|
||||||
def id
|
|
||||||
@instance.id
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class DataAccessor < Sunspot::Adapters::DataAccessor
|
|
||||||
def load(id)
|
|
||||||
criteria(id).first
|
|
||||||
end
|
|
||||||
|
|
||||||
def load_all(ids)
|
|
||||||
criteria(ids)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def criteria(id)
|
|
||||||
@clazz.criteria.id(id)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1 +0,0 @@
|
||||||
require 'sunspot/mongoid'
|
|
|
@ -1,67 +0,0 @@
|
||||||
# Generated by jeweler
|
|
||||||
# DO NOT EDIT THIS FILE DIRECTLY
|
|
||||||
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
|
||||||
# -*- encoding: utf-8 -*-
|
|
||||||
|
|
||||||
Gem::Specification.new do |s|
|
|
||||||
s.name = %q{sunspot_mongoid}
|
|
||||||
s.version = "0.4.1"
|
|
||||||
|
|
||||||
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
|
||||||
s.authors = ["jugyo"]
|
|
||||||
s.date = %q{2011-03-04}
|
|
||||||
s.description = %q{A Sunspot wrapper for Mongoid that is like sunspot_rails.}
|
|
||||||
s.email = %q{jugyo.org@gmail.com}
|
|
||||||
s.extra_rdoc_files = [
|
|
||||||
"LICENSE",
|
|
||||||
"README.md"
|
|
||||||
]
|
|
||||||
s.files = [
|
|
||||||
".document",
|
|
||||||
"LICENSE",
|
|
||||||
"README.md",
|
|
||||||
"Rakefile",
|
|
||||||
"VERSION",
|
|
||||||
"examples/example.rb",
|
|
||||||
"init.rb",
|
|
||||||
"lib/sunspot/mongoid.rb",
|
|
||||||
"lib/sunspot_mongoid.rb",
|
|
||||||
"sunspot_mongoid.gemspec",
|
|
||||||
"test/helper.rb",
|
|
||||||
"test/test_sunspot_mongoid.rb"
|
|
||||||
]
|
|
||||||
s.homepage = %q{http://github.com/jugyo/sunspot_mongoid}
|
|
||||||
s.require_paths = ["lib"]
|
|
||||||
s.rubygems_version = %q{1.6.0}
|
|
||||||
s.summary = %q{A Sunspot wrapper for Mongoid.}
|
|
||||||
s.test_files = [
|
|
||||||
"examples/example.rb",
|
|
||||||
"test/helper.rb",
|
|
||||||
"test/test_sunspot_mongoid.rb"
|
|
||||||
]
|
|
||||||
|
|
||||||
if s.respond_to? :specification_version then
|
|
||||||
s.specification_version = 3
|
|
||||||
|
|
||||||
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
|
||||||
s.add_runtime_dependency(%q<mongoid>, [">= 0"])
|
|
||||||
s.add_runtime_dependency(%q<sunspot>, [">= 1.1.0"])
|
|
||||||
s.add_runtime_dependency(%q<sunspot_rails>, [">= 1.1.0"])
|
|
||||||
s.add_development_dependency(%q<shoulda>, [">= 0"])
|
|
||||||
s.add_development_dependency(%q<rr>, [">= 0"])
|
|
||||||
else
|
|
||||||
s.add_dependency(%q<mongoid>, [">= 0"])
|
|
||||||
s.add_dependency(%q<sunspot>, [">= 1.1.0"])
|
|
||||||
s.add_dependency(%q<sunspot_rails>, [">= 1.1.0"])
|
|
||||||
s.add_dependency(%q<shoulda>, [">= 0"])
|
|
||||||
s.add_dependency(%q<rr>, [">= 0"])
|
|
||||||
end
|
|
||||||
else
|
|
||||||
s.add_dependency(%q<mongoid>, [">= 0"])
|
|
||||||
s.add_dependency(%q<sunspot>, [">= 1.1.0"])
|
|
||||||
s.add_dependency(%q<sunspot_rails>, [">= 1.1.0"])
|
|
||||||
s.add_dependency(%q<shoulda>, [">= 0"])
|
|
||||||
s.add_dependency(%q<rr>, [">= 0"])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
require 'rubygems'
|
|
||||||
require 'test/unit'
|
|
||||||
require 'shoulda'
|
|
||||||
require 'rr'
|
|
||||||
|
|
||||||
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
|
||||||
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
|
||||||
require 'sunspot_mongoid'
|
|
||||||
|
|
||||||
class Test::Unit::TestCase
|
|
||||||
include RR::Adapters::TestUnit
|
|
||||||
end
|
|
|
@ -1,51 +0,0 @@
|
||||||
require 'helper'
|
|
||||||
|
|
||||||
#
|
|
||||||
# NOTE: I think tests are too few...
|
|
||||||
#
|
|
||||||
class TestSunspotMongoid < Test::Unit::TestCase
|
|
||||||
class Foo
|
|
||||||
include Mongoid::Document
|
|
||||||
field :title
|
|
||||||
|
|
||||||
include Sunspot::Mongoid
|
|
||||||
searchable do
|
|
||||||
text :title
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class Bar
|
|
||||||
include Mongoid::Document
|
|
||||||
field :title
|
|
||||||
|
|
||||||
include Sunspot::Mongoid
|
|
||||||
searchable(:auto_index => false, :auto_remove => false) do
|
|
||||||
text :title
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'default' do
|
|
||||||
should 'sunspot_options is specified' do
|
|
||||||
assert Foo.sunspot_options == {:include => []}
|
|
||||||
assert Bar.sunspot_options == {:auto_index=>false, :auto_remove=>false, :include=>[]}
|
|
||||||
end
|
|
||||||
|
|
||||||
should 'be called Sunspot.setup when call Foo.searchable' do
|
|
||||||
mock(Sunspot).setup(Foo)
|
|
||||||
Foo.searchable
|
|
||||||
end
|
|
||||||
|
|
||||||
should 'get as text_fields from Sunspot::Setup' do
|
|
||||||
text_field = Sunspot::Setup.for(Foo).all_text_fields.first
|
|
||||||
assert text_field.type == Sunspot::Type::TextType.instance
|
|
||||||
assert text_field.name == :title
|
|
||||||
end
|
|
||||||
|
|
||||||
should 'search' do
|
|
||||||
options = {}
|
|
||||||
mock.proxy(Foo).solr_execute_search(options)
|
|
||||||
mock(Sunspot).new_search(Foo) { mock(Object.new).execute }
|
|
||||||
Foo.search(options)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
Reference in New Issue