Skip to content
This repository has been archived by the owner on Mar 3, 2020. It is now read-only.

Commit

Permalink
Merge f017484 into 8792e28
Browse files Browse the repository at this point in the history
  • Loading branch information
nning committed Feb 18, 2014
2 parents 8792e28 + f017484 commit 6e17048
Show file tree
Hide file tree
Showing 14 changed files with 269 additions and 96 deletions.
3 changes: 3 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ rvm:
notifications:
email: false

services:
- elasticsearch

before_install:
- gem install bundler

Expand Down
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ gem 'jquery-rails' # jQuery
gem 'kaminari' # Pagination
gem 'paper_trail', '~> 2.7.2' # Change history
gem 'rails_config' # For configuration
gem 'searchkick' # Efficient search
gem 'simple_form' # DRY form
gem 'yaml_db' # Database dump

Expand Down
16 changes: 16 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ GEM
activesupport (3.2.16)
i18n (~> 0.6, >= 0.6.4)
multi_json (~> 1.0)
ansi (1.4.3)
arel (3.0.3)
atomic (1.1.14)
atomic (1.1.14-java)
Expand Down Expand Up @@ -126,6 +127,7 @@ GEM
has_scope (0.6.0.rc)
actionpack (>= 3.2, < 5)
activesupport (>= 3.2, < 5)
hashr (0.0.22)
highline (1.6.20)
hike (1.2.3)
i18n (0.6.9)
Expand Down Expand Up @@ -208,6 +210,9 @@ GEM
railties (~> 3.2.0)
sass (>= 3.1.10)
tilt (~> 1.3)
searchkick (0.5.2)
tire
tire-contrib
sexp_processor (4.4.1)
shoulda (3.5.0)
shoulda-context (~> 1.0, >= 1.0.1)
Expand Down Expand Up @@ -253,6 +258,16 @@ GEM
atomic
tilt (1.4.1)
tins (1.0.0)
tire (0.6.2)
activemodel (>= 3.0)
activesupport
ansi
hashr (~> 0.0.19)
multi_json (~> 1.3)
rake
rest-client (~> 1.6)
tire-contrib (0.1.3)
tire
treetop (1.4.15)
polyglot
polyglot (>= 0.3.1)
Expand Down Expand Up @@ -302,6 +317,7 @@ DEPENDENCIES
rails_config
rake
sass-rails
searchkick
shoulda
simple_form
sqlite3
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Features
* Permissions and Roles for Mailboxes to e.g. manage a certain domain.
* Responsive web user interface.
* Change log for review and reverting changes.
* Search (optionally based on SQL or elasticsearch).

Installation
------------
Expand All @@ -31,6 +32,9 @@ postfix and dovecot are to be found in the doc directory of the code repository.

### Local testing

It is assumed, elasticsearch is installed and running. If you want to use SQL
based search, set `elasticsearch: false` in `config/settings.yml`.

git clone git://github.com/nning/moeil.git
cd moeil
ln -s database.yml.example config/database.yml
Expand All @@ -43,6 +47,9 @@ postfix and dovecot are to be found in the doc directory of the code repository.

### OpenShift

**(Note, that the OpenShift guide does currently not contain instructions for
elasticsearch.)**

First steps happen in your local terminal. So this is for creating an OpenShift
Ruby 1.9 application with a PostgreSQL 9.2 cartridge:

Expand Down
9 changes: 8 additions & 1 deletion app/models/alias.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,16 @@ class Alias < ActiveRecord::Base

default_scope order('username asc')

validates :goto, presence: true

has_paper_trail

validates :goto, presence: true
searchkick word_middle: [:description, :username]
# Search fields options includable in search on model.
SEARCH_FIELDS = [
{ description: :word_middle },
{ username: :word_middle }
]

# E-Mail address.
def email
Expand Down
9 changes: 8 additions & 1 deletion app/models/domain.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,16 @@ class Domain < ActiveRecord::Base
format: { with: /\A[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?\z/ix },
presence: true

before_save -> { name.downcase! }

has_paper_trail

before_save -> { name.downcase! }
searchkick word_middle: [:name, :description]
# Search fields options includable in search on model.
SEARCH_FIELDS = [
{ name: :word_middle },
{ description: :word_middle }
]

# Aliases count for simple_form.
def aliases_count
Expand Down
23 changes: 15 additions & 8 deletions app/models/mailbox.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,11 @@ class Mailbox < ActiveRecord::Base
has_many :permissions, dependent: :destroy, as: :subject
has_one :relocation, dependent: :destroy

devise :database_authenticatable, :encryptable

attr_accessible :active, :admin, :domain_id, :mail_location, :name, :password,
:password_confirmation, :quota, :username

before_save :create_relocation

default_scope order('username asc')

has_paper_trail

default_value_for :quota, Settings.default_quota

validates :password,
presence: {
if: :password_required?
Expand All @@ -33,6 +25,21 @@ class Mailbox < ActiveRecord::Base

validates :encrypted_password, presence: true

before_save :create_relocation

devise :database_authenticatable, :encryptable

has_paper_trail

default_value_for :quota, Settings.default_quota

searchkick word_middle: [:name, :username]
# Search fields options includable in search on model.
SEARCH_FIELDS = [
{ name: :word_middle },
{ username: :word_middle }
]

# Aliases pointing to Mailbox.
def aliases
Alias.where('goto like ?', email)
Expand Down
13 changes: 13 additions & 0 deletions config/initializers/string_utilities.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Monkey-patch the String class.
class String
# Random index in string.
def random_index
rand(size - 1)
end

# Random substring of string.
def random_substring
a = [0, 0].map { random_index }.sort
self[a[0]..a[1]]
end
end
2 changes: 2 additions & 0 deletions config/settings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ minimal_password_length: 8

blocked_usernames:
- dummy

elasticsearch: true
41 changes: 15 additions & 26 deletions lib/search.rb
Original file line number Diff line number Diff line change
@@ -1,37 +1,26 @@
# Search for resources.
class Search
module Search
# Results for query.
def self.for(mailbox, query)
results = {}
query = "%#{query}%"
def self.for(mailbox, query, sql: !Settings.elasticsearch)
haystack = ::Domain.managable(mailbox)
haystack_ids = haystack.pluck(:id)

haystack = Domain.managable(mailbox)
query = "%#{query}%" if sql

domains = haystack.where('name like ?', query)
domains.each { |domain| results[domain] ||= [] }
results = {}

haystack.all.each do |domain|
[:mailboxes, :aliases].each do |model|
domain.send(model).where('username like ?', query).each do |result|
results[result.domain] ||= []
results[result.domain] << result
end
Search::Domain.search(haystack, query, sql: sql).each do |domain|
results[domain] ||= []
end

[Alias, Mailbox].each do |model|
a = model.where(domain_id: haystack_ids)
Search::Address.search(a, query, sql: sql).each do |result|
results[result.domain] ||= []
results[result.domain] << result
end
end

results
end

# Random substring of string.
def self.random_substring(string)
x = [0, 0].map { random_index(string) }.sort
string[x[0]..x[1]]
end

private

# Random index in string.
def self.random_index(string)
rand(string.size - 1)
end
end
27 changes: 27 additions & 0 deletions lib/search/address.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Search for aliases or mailboxes.
module Search::Address
@model = nil

# Search either via SQL or indexed (depending on settings or argument).
def self.search(haystack, needle, sql: !Settings.elasticsearch)
@model = Object.const_get haystack.model_name

if sql
sql_search(haystack, needle)
else
indexed_search(haystack, needle)
end
end

private

# Search indexed.
def self.indexed_search(haystack, needle)
haystack.search(needle, fields: @model.const_get('SEARCH_FIELDS'))
end

# Search via SQL.
def self.sql_search(haystack, needle)
haystack.where('username like ?', needle)
end
end
23 changes: 23 additions & 0 deletions lib/search/domain.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Search for domains.
module Search::Domain
# Search either via SQL or indexed (depending on settings or argument).
def self.search(haystack, needle, sql: !Settings.elasticsearch)
if sql
sql_search(haystack, needle)
else
indexed_search(haystack, needle)
end
end

private

# Search indexed.
def self.indexed_search(haystack, needle)
haystack.search(needle, fields: Domain::SEARCH_FIELDS)
end

# Search via SQL.
def self.sql_search(haystack, needle)
haystack.where('name like ?', needle)
end
end
8 changes: 3 additions & 5 deletions test/functional/admin/searches_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,15 @@ class Admin::SearchesControllerTest < ActionController::TestCase
setup do
@mailbox = FactoryGirl.create :mailbox, admin: true
@domain = @mailbox.domain
@alias = FactoryGirl.create :alias, domain: @domain
[Alias, Domain, Mailbox].map(&:reindex)
sign_in @mailbox
end

context 'search' do
setup do
@alias = FactoryGirl.create :alias, domain: @domain
end

context 'on POST to search (for Domain)' do
setup do
post :search, q: Search.random_substring(@domain.name)
post :search, q: @domain.name.random_substring
end

should respond_with :success
Expand Down
Loading

0 comments on commit 6e17048

Please sign in to comment.