kuhsaft 1.6.0 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -226,6 +226,29 @@ Simply override the default partial for the main navigation in your app with you
226
226
  * Implement the `fulltext` method on your brick, return anything you want to be searchable.
227
227
  * Customize the edit form behaviour of your brick by overriding methods like `to_style_class?`. See the `Brick` and `BrickList` files for more methods.
228
228
 
229
+ ## Integrating search
230
+
231
+ Kuhsaft supports fulltext search when using PostgreSQL with a simple
232
+ LIKE fallback for any other ActiveRecord DB.
233
+
234
+ Add a call to the `search_page_form` helper in your views. This renders
235
+ the default search form. The query will be executed by kuhsaft.
236
+
237
+ # e.g. _footer.html.haml
238
+ = search_page_form
239
+
240
+ To customize the search and result views you can add your own partials
241
+ to your rails app. The following partials are overridable.
242
+
243
+ app/views/kuhsaft/search
244
+ ├── _form.html.haml # Search form
245
+ ├── _results.html.haml # Results list (@pages)
246
+ └── _results_entry.html.haml # Single result entry (@page)
247
+
248
+ When using PostgreSQL, an additional attribute `excerpt` will be
249
+ available on the page model. It includes a highlighted excerpt of the
250
+ matching `fulltext` column.
251
+
229
252
  # LICENSE
230
253
 
231
254
  See the file LICENSE.
@@ -2,6 +2,13 @@ module Kuhsaft
2
2
  class PagesController < ::ApplicationController
3
3
  respond_to :html
4
4
 
5
+ def index
6
+ @search = params[:search]
7
+ if @search.present?
8
+ @pages = Kuhsaft::Page.unscoped.published.content_page.search(@search)
9
+ end
10
+ end
11
+
5
12
  def show
6
13
  url = locale.to_s
7
14
  url += "/#{params[:url]}" if params[:url].present?
@@ -5,6 +5,18 @@ module Kuhsaft
5
5
  def render_language_switch?
6
6
  I18n.available_locales.size > 1
7
7
  end
8
+
9
+ def link_to_content_locale(locale)
10
+ action = params[:action]
11
+ if params[:action] == 'create'
12
+ action = 'new'
13
+ elsif params[:action] == 'update'
14
+ action = 'edit'
15
+ end
16
+
17
+ link_to locale.to_s.upcase, url_for(
18
+ :action => action, :content_locale => locale)
19
+ end
8
20
  end
9
21
  end
10
22
  end
@@ -65,4 +65,14 @@ module PagesHelper
65
65
  @content << content_tag(:p, t('kuhsaft.text_bricks.text_brick.read_less'), :class => 'read-less-text')
66
66
  end
67
67
  end
68
+
69
+ def search_page_form
70
+ form_tag kuhsaft.pages_path, :method => :get, :class => 'form-inline' do
71
+ if block_given?
72
+ yield
73
+ else
74
+ render 'kuhsaft/search/form'
75
+ end
76
+ end
77
+ end
68
78
  end
@@ -29,6 +29,13 @@ module Kuhsaft
29
29
  self.position ||= has_siblings? ? brick_list.bricks.maximum(:position).to_i + 1 : 1
30
30
  end
31
31
 
32
+ after_save do
33
+ # TODO: replace callback with fulltext row on each
34
+ # searchable model
35
+ brick_list.update_fulltext
36
+ brick_list.save!
37
+ end
38
+
32
39
  def to_edit_partial_path
33
40
  path = self.to_partial_path.split '/'
34
41
  path << 'edit'
@@ -1,26 +1,49 @@
1
1
  class Kuhsaft::Page < ActiveRecord::Base
2
+ include Kuhsaft::Engine.routes.url_helpers
2
3
  include Kuhsaft::Orderable
3
4
  include Kuhsaft::Translatable
4
5
  include Kuhsaft::BrickList
6
+ include Kuhsaft::Searchable
5
7
 
6
8
  has_ancestry
7
9
  acts_as_brick_list
8
10
 
9
- translate :title, :slug, :keywords, :description, :body, :redirect_url, :url, :fulltext
10
- attr_accessible :title, :slug, :redirect_url, :url, :page_type, :parent_id, :keywords, :description, :published
11
+ translate :title,
12
+ :slug,
13
+ :keywords,
14
+ :description,
15
+ :body,
16
+ :redirect_url,
17
+ :url
18
+
19
+ attr_accessible :title,
20
+ :slug,
21
+ :redirect_url,
22
+ :url,
23
+ :page_type,
24
+ :parent_id,
25
+ :keywords,
26
+ :description,
27
+ :published
11
28
 
12
29
  default_scope order('position ASC')
13
30
 
14
31
  scope :published, where(:published => Kuhsaft::PublishState::PUBLISHED)
15
- scope :search, lambda{ |term| published.where("`#{locale_attr(:fulltext)}` LIKE ?", "%#{term}%") }
16
- scope :navigation, lambda{ |slug| where(locale_attr(:slug) => slug).where(locale_attr(:page_type) => Kuhsaft::PageType::NAVIGATION) }
17
32
 
18
- before_validation :create_slug, :create_url, :collect_fulltext
33
+ # TODO: cleanup page_types (content pages => nil or PageType::CONTENT
34
+ scope :content_page, where(
35
+ ["page_type is NULL or page_type = ?",
36
+ Kuhsaft::PageType::CONTENT])
37
+
38
+ scope :navigation, lambda{ |slug|
39
+ where(locale_attr(:slug) => slug).where(
40
+ locale_attr(:page_type) => Kuhsaft::PageType::NAVIGATION) }
41
+
42
+ before_validation :create_slug, :create_url
19
43
 
20
44
  validates :title, :presence => true
21
45
  validates :slug, :presence => true
22
46
  validates :redirect_url, :presence => true, :if => :redirect?
23
- #validates :url, :uniqueness => true, :unless => :navigation?
24
47
 
25
48
  class << self
26
49
  def flat_tree(pages = nil)
@@ -72,19 +95,30 @@ class Kuhsaft::Page < ActiveRecord::Base
72
95
  if bricks.count == 0 && children.count > 0
73
96
  children.first.link
74
97
  else
75
- "/#{url}"
98
+ url_with_locale
76
99
  end
77
100
  end
78
101
 
102
+ # TODO: needs naming and routing refactoring (url/locale/path/slug)
103
+ def path_segments
104
+ paths = parent.present? ? parent.path_segments : []
105
+ paths << slug unless navigation?
106
+ paths
107
+ end
108
+
109
+ def url_without_locale
110
+ path_segments.join('/')
111
+ end
112
+
113
+ def url_with_locale
114
+ opts = { :locale => I18n.locale }
115
+ url = url_without_locale
116
+ opts[:url] = url if url.present?
117
+ page_path(opts)
118
+ end
119
+
79
120
  def create_url
80
- complete_slug = ''
81
- if parent.present?
82
- complete_slug << parent.url.to_s
83
- else
84
- complete_slug = "#{I18n.locale}"
85
- end
86
- complete_slug << "/#{self.slug}" unless navigation?
87
- self.url = complete_slug
121
+ self.url = url_with_locale[1..-1]
88
122
  end
89
123
 
90
124
  def create_slug
@@ -92,10 +126,6 @@ class Kuhsaft::Page < ActiveRecord::Base
92
126
  self.slug = title.downcase.parameterize if has_slug
93
127
  end
94
128
 
95
- def collect_fulltext
96
- self.fulltext =[super, title.to_s, keywords.to_s, description.to_s].join(' ')
97
- end
98
-
99
129
  def nesting_name
100
130
  num_dashes = parent_pages.size
101
131
  num_dashes = 0 if num_dashes < 0
@@ -3,9 +3,9 @@ module Kuhsaft
3
3
  REDIRECT = 'redirect'
4
4
  NAVIGATION = 'navigation'
5
5
  CONTENT = ''
6
-
6
+
7
7
  def self.all
8
8
  [CONTENT, REDIRECT, NAVIGATION]
9
9
  end
10
10
  end
11
- end
11
+ end
@@ -1,5 +1,10 @@
1
+ require 'htmlentities'
2
+
1
3
  module Kuhsaft
2
4
  class TextBrick < Brick
5
+ include ActionView::Helpers::SanitizeHelper
6
+ HTML_ENTITIES = HTMLEntities.new
7
+
3
8
  attr_accessible :text, :read_more_text
4
9
 
5
10
  def user_can_add_childs?
@@ -7,7 +12,12 @@ module Kuhsaft
7
12
  end
8
13
 
9
14
  def collect_fulltext
10
- [super, text, read_more_text].join(' ')
15
+ HTML_ENTITIES.decode(
16
+ strip_tags([
17
+ text,
18
+ read_more_text
19
+ ].compact.join(' ')).squish
20
+ )
11
21
  end
12
22
  end
13
23
  end
@@ -3,5 +3,5 @@
3
3
  %ul.nav.nav-pills
4
4
  - I18n.available_locales.each do |locale|
5
5
  %li{ :class => (:active if I18n.locale.to_s == locale.to_s) }
6
- = link_to locale.to_s.upcase, url_for(:content_locale => locale)
6
+ = link_to_content_locale(locale)
7
7
  .clear
@@ -0,0 +1,7 @@
1
+ .search.has-section-space-bottom
2
+ .row-fluid
3
+ %h1=t 'kuhsaft.search.results.title'
4
+ .row-fluid
5
+ = render 'kuhsaft/search/results'
6
+ .row-fluid
7
+ = search_page_form
@@ -0,0 +1,2 @@
1
+ = text_field_tag 'search', params[:search], :placeholder => t('.placeholder')
2
+ = submit_tag t('.search')
@@ -0,0 +1,8 @@
1
+ - if @pages.present?
2
+ %h2.success
3
+ = t('.number-of-results', :count => @pages.count)
4
+ %ul.search-results.success
5
+ - @pages.each do |page|
6
+ %li= render 'kuhsaft/search/results_entry', :page => page
7
+ - else
8
+ %h2.no-results= t('.no-results')
@@ -0,0 +1,13 @@
1
+ .link
2
+ = link_to page.link do
3
+ %h4= page.title
4
+ %h5= kuhsaft.page_url(:url => page.url_without_locale)
5
+ .summary
6
+ -if page.excerpt.present?
7
+ .excerpt!= page.excerpt
8
+ -elsif page.fulltext.present?
9
+ .fulltext!= excerpt(highlight(page.fulltext, @search), @search)
10
+
11
+ -if page.keywords.present?
12
+ .keywords
13
+ = page.keywords
@@ -0,0 +1,12 @@
1
+ de:
2
+ kuhsaft:
3
+ search:
4
+ results:
5
+ title: 'Suchresultate'
6
+ number-of-results:
7
+ one: '1 Resultat'
8
+ other: '%{count} Resultate'
9
+ no-results: 'Keine Resultate'
10
+ form:
11
+ placeholder: 'Ihre Suche'
12
+ search: 'Suchen'
@@ -0,0 +1,12 @@
1
+ en:
2
+ kuhsaft:
3
+ search:
4
+ results:
5
+ title: Search results
6
+ number-of-results:
7
+ one: '1 result'
8
+ other: '%{count} results'
9
+ no-results: 'No results'
10
+ form:
11
+ placeholder: 'Search'
12
+ search: 'Search'
@@ -10,7 +10,9 @@ Kuhsaft::Engine.routes.draw do
10
10
  end
11
11
 
12
12
  scope ":locale", :locale => /#{I18n.available_locales.join('|')}/ do
13
- resources :pages, :only => [:index]
14
- match '*url' => 'pages#show'
13
+ resources :pages,
14
+ :only => [:index],
15
+ :defaults => { :locale => I18n.locale }
16
+ match '(*url)' => 'pages#show', :as => :page
15
17
  end
16
18
  end
@@ -0,0 +1,12 @@
1
+ class RegenerateFulltext < ActiveRecord::Migration
2
+ def up
3
+ Kuhsaft::Page.all.each do |p|
4
+ I18n.available_locales.each do |locale|
5
+ I18n.with_locale do
6
+ puts "Save #{p.locale_attr(:fulltext)} #{p.link}"
7
+ p.save!
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -2,6 +2,7 @@ module Kuhsaft
2
2
  require 'kuhsaft/engine'
3
3
  require 'kuhsaft/orderable'
4
4
  require 'kuhsaft/translatable'
5
+ require 'kuhsaft/searchable'
5
6
  require 'kuhsaft/brick_list'
6
7
  require 'kuhsaft/partial_extractor'
7
8
  require 'simple_form'
@@ -3,7 +3,10 @@ module Kuhsaft
3
3
 
4
4
  def self.included(base)
5
5
  def base.acts_as_brick_list
6
- self.has_many :bricks, :class_name => 'Kuhsaft::Brick', :dependent => :destroy, :as => :brick_list
6
+ self.has_many :bricks,
7
+ :class_name => 'Kuhsaft::Brick',
8
+ :dependent => :destroy,
9
+ :as => :brick_list
7
10
  end
8
11
  end
9
12
 
@@ -0,0 +1,72 @@
1
+ require 'active_support/concern'
2
+ require 'pg_search'
3
+
4
+ module Kuhsaft
5
+ module Searchable
6
+ extend ActiveSupport::Concern
7
+
8
+ DICTIONARIES = {
9
+ :en => 'english',
10
+ :de => 'german',
11
+ }
12
+
13
+ def update_fulltext
14
+ self.fulltext = collect_fulltext
15
+ end
16
+
17
+ included do
18
+ unless included_modules.include?(BrickList)
19
+ raise 'Kuhsaft::Searchable needs Kuhsaft::BrickList to be included'
20
+ end
21
+
22
+ if included_modules.include?(Translatable)
23
+ translate :fulltext
24
+ else
25
+ attr_accessible :fulltext
26
+ end
27
+
28
+ before_validation :update_fulltext
29
+
30
+ if ActiveRecord::Base.connection.instance_values['config'][:adapter] == 'postgresql'
31
+ include ::PgSearch
32
+ cb = lambda do |query|
33
+ {
34
+ :against => {
35
+ locale_attr(:title) => 'A',
36
+ locale_attr(:keywords) => 'B',
37
+ locale_attr(:description) => 'C',
38
+ locale_attr(:fulltext) => 'C',
39
+ },
40
+ :query => query,
41
+ :using => { :tsearch => { :dictionary => DICTIONARIES[I18n.locale] || 'simple' }}
42
+ }
43
+ end
44
+ pg_search_scope :search_without_excerpt, cb
45
+ scope :search, lambda { |query|
46
+ ts_headline = sanitize_sql_array([
47
+ "ts_headline(%s, plainto_tsquery('%s')) AS excerpt",
48
+ locale_attr(:fulltext),
49
+ query
50
+ ])
51
+ search_without_excerpt(query).select(ts_headline)
52
+ }
53
+ else
54
+ # TODO: Tests run in this branch because dummy app uses mysql. Change it!
55
+ # define empty fallback excerpt attribute
56
+ attr_reader :excerpt
57
+ scope :search, lambda { |query|
58
+ if query.is_a? Hash
59
+ where("#{query.first[0]} LIKE ?", "%#{query.first[1]}%")
60
+ else
61
+ stmt = ""
62
+ stmt += "#{locale_attr(:keywords)} LIKE ? OR "
63
+ stmt += "#{locale_attr(:title)} LIKE ? OR "
64
+ stmt += "#{locale_attr(:description)} LIKE ? OR "
65
+ stmt += "#{locale_attr(:fulltext)} LIKE ?"
66
+ where(stmt, *(["%#{query}%"] * 4))
67
+ end
68
+ }
69
+ end
70
+ end
71
+ end
72
+ end
@@ -1,3 +1,3 @@
1
1
  module Kuhsaft
2
- VERSION = "1.6.0"
2
+ VERSION = "1.7.0"
3
3
  end
@@ -3,6 +3,24 @@ require 'spec_helper'
3
3
  describe Kuhsaft::PagesController do
4
4
  subject { described_class }
5
5
 
6
+ describe '#index' do
7
+ before do
8
+ @pages = [
9
+ create(:page, :page_type => Kuhsaft::PageType::CONTENT, :published => true, :fulltext_de => 'foobar'),
10
+ create(:page, :page_type => Kuhsaft::PageType::CONTENT, :published => true, :fulltext_de => 'barfoo')
11
+ ]
12
+ end
13
+
14
+ context 'with search parameter' do
15
+ it 'assigns the search results' do
16
+ I18n.with_locale :de do
17
+ get(:index, { :use_route => :kuhsaft, :search => 'foobar' })
18
+ end
19
+ assigns(:pages).should eq([@pages.first])
20
+ end
21
+ end
22
+ end
23
+
6
24
  describe '#show' do
7
25
  describe 'redirect' do
8
26
  around(:each) do |example|
@@ -14,7 +32,7 @@ describe Kuhsaft::PagesController do
14
32
  context 'when page is not a redirect page' do
15
33
  it 'responds with page' do
16
34
  page = FactoryGirl.create(:page, :slug => 'dumdidum', :url => 'de/dumdidum')
17
- get :show, { :url => page.slug, :use_route => :kuhsaft, :locale => :de }
35
+ get :show, { :url => page.slug, :use_route => :kuhsaft }
18
36
  assigns(:page).should eq(page)
19
37
  end
20
38
  end
@@ -22,7 +40,7 @@ describe Kuhsaft::PagesController do
22
40
  context 'when page is a redirect page' do
23
41
  it 'redirects to the redirected url' do
24
42
  page = FactoryGirl.create(:page, :page_type => 'redirect', :slug => 'dumdidum', :url => 'de/dumdidum', :redirect_url => 'de/redirect_page')
25
- get :show, { :url => page.slug, :use_route => :kuhsaft, :locale => :de }
43
+ get :show, { :url => page.slug, :use_route => :kuhsaft }
26
44
  expect(response).to redirect_to("/de/redirect_page")
27
45
  end
28
46
  end
@@ -52,5 +70,29 @@ describe Kuhsaft::PagesController do
52
70
  end
53
71
  end
54
72
  end
73
+
74
+ describe 'page type' do
75
+ around(:each) do |example|
76
+ I18n.with_locale :de do
77
+ example.run
78
+ end
79
+ end
80
+
81
+ context 'when page is not a redirect page' do
82
+ it 'responds with page' do
83
+ page = FactoryGirl.create(:page, :slug => 'dumdidum', :url => 'de/dumdidum')
84
+ get :show, { :url => page.slug, :use_route => :kuhsaft }
85
+ assigns(:page).should eq(page)
86
+ end
87
+ end
88
+
89
+ context 'when page is a redirect page' do
90
+ it 'redirects to the redirected url' do
91
+ page = FactoryGirl.create(:page, :page_type => 'redirect', :slug => 'dumdidum', :url => 'de/dumdidum', :redirect_url => 'de/redirect_page')
92
+ get :show, { :url => page.slug, :use_route => :kuhsaft }
93
+ expect(response).to redirect_to("/de/redirect_page")
94
+ end
95
+ end
96
+ end
55
97
  end
56
98
  end
@@ -1,3 +1,4 @@
1
1
  class ApplicationController < ActionController::Base
2
2
  protect_from_forgery
3
+ helper Kuhsaft::Engine.helpers
3
4
  end
@@ -13,12 +13,27 @@ describe 'Cms/Pages' do
13
13
  end
14
14
 
15
15
  describe '#create' do
16
- it 'creates a new page' do
17
- expect { click_on 'Create Seite' }.to change(Kuhsaft::Page, :count).by(1)
16
+ context 'when page is valid' do
17
+ it 'creates a new page' do
18
+ expect { click_on 'Create Seite' }.to change(Kuhsaft::Page, :count).by(1)
19
+ end
20
+
21
+ it 'is not possible to change the value in url' do
22
+ page.find('#page_url')['disabled'].should be_true
23
+ end
18
24
  end
19
25
 
20
- it 'is not possible to change the value in url' do
21
- page.find('#page_url')['disabled'].should be_true
26
+ context 'when page is invalid' do
27
+ it 'does not create a routing error by switching the locale' do
28
+ @page = FactoryGirl.create(:page, :title => 'DummyPage', :title_en => 'DummyEN', :slug => 'dummy_page')
29
+ visit kuhsaft.edit_cms_page_path(@page)
30
+ fill_in 'page_title', :with => ''
31
+ click_on 'Update Seite'
32
+ within '.nav-pills' do
33
+ click_on 'EN'
34
+ end
35
+ page.should have_content(@page.title_en)
36
+ end
22
37
  end
23
38
  end
24
39
 
@@ -0,0 +1,80 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'pages#index' do
4
+ context 'with search parameter' do
5
+ let! :page1 do
6
+ p = create :page,
7
+ :page_type => Kuhsaft::PageType::CONTENT,
8
+ :published => true,
9
+ :title => 'Chromodorididae Ardeadoris'
10
+ p.bricks << Kuhsaft::TextBrick.new(:locale => I18n.locale, :text => "#{'foo bar' * 300} Chromodorididae #{'foo bar' * 300}")
11
+ p.save!
12
+ p
13
+ end
14
+
15
+ let! :page2 do
16
+ create :page,
17
+ :page_type => Kuhsaft::PageType::CONTENT,
18
+ :published => true,
19
+ :title => 'Chromodorididae Berlanguella'
20
+ end
21
+
22
+ let! :page3 do
23
+ create :page,
24
+ :page_type => Kuhsaft::PageType::CONTENT,
25
+ :published => true,
26
+ :title => 'Gastropoda'
27
+ end
28
+
29
+ context 'with fulltext' do
30
+ before do
31
+ visit kuhsaft.pages_path(:locale => :en, :search => 'Chromodorididae')
32
+ end
33
+
34
+ it 'highlights search term in preview' do
35
+ within("ul.search-results strong.highlight") do
36
+ page.should have_content('Chromodorididae')
37
+ end
38
+ end
39
+
40
+ it 'truncates the text' do
41
+ find('.summary .fulltext').text.length.should < 200
42
+ end
43
+ end
44
+
45
+ context 'with multiple matches' do
46
+ before do
47
+ visit kuhsaft.pages_path(:locale => :en, :search => 'Chromodorididae')
48
+ end
49
+
50
+ it 'renders match count' do
51
+ page.should have_content("2 results")
52
+ end
53
+
54
+ it 'renders the search results list' do
55
+ within("ul.search-results.success") do
56
+ page.should have_content('Chromodorididae Ardeadoris')
57
+ page.should have_content('Chromodorididae Berlanguella')
58
+ page.should_not have_content('Gastropoda')
59
+ end
60
+ end
61
+
62
+ it 'renders links to the pages' do
63
+ within("ul.search-results.success") do
64
+ page.should have_link('Chromodorididae Ardeadoris', href: page1.link)
65
+ page.should have_link('Chromodorididae Berlanguella', href: page2.link)
66
+ end
67
+ end
68
+ end
69
+
70
+ context 'without matches' do
71
+ before do
72
+ visit kuhsaft.pages_path(:locale => :en, :search => 'foobar')
73
+ end
74
+
75
+ it 'renders match count' do
76
+ page.should have_content("No results")
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ describe PagesHelper do
4
+ describe '#search_page_form' do
5
+
6
+ context 'without block' do
7
+ it 'renders the default search form' do
8
+ form = search_page_form
9
+ form.should have_css('form.form-inline')
10
+ form.should have_css('input[type=text]')
11
+ form.should have_css('input[type=submit]')
12
+ end
13
+ end
14
+
15
+ context 'with block' do
16
+ it 'calls the given block' do
17
+ expect { |b| search_page_form(&b) }.to yield_with_no_args
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+
3
+ describe Kuhsaft::Searchable do
4
+
5
+ context 'with missing includes' do
6
+ it 'raises exteption when class does not include Kuhsaft::Bricklist' do
7
+ expect {
8
+ class Foo
9
+ include Kuhsaft::Searchable
10
+ end
11
+ }.to raise_error(/needs Kuhsaft::BrickList to be included/)
12
+ end
13
+ end
14
+
15
+ context 'with Bricklist included' do
16
+ class SearchableDemo < ActiveRecord::Base
17
+ include Kuhsaft::BrickList
18
+ end
19
+
20
+ context 'without postgresql' do
21
+ it 'initializes scope' do
22
+ ActiveRecord::Base.connection.instance_values.should_not == 'postgresql'
23
+ SearchableDemo.should_receive :scope
24
+ SearchableDemo.class_eval do
25
+ include Kuhsaft::Searchable
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -107,4 +107,28 @@ describe Kuhsaft::Brick do
107
107
  brick.uploader?.should be_false
108
108
  end
109
109
  end
110
+
111
+ describe '#after_save' do
112
+ describe 'update_fulltext' do
113
+ let! :brick do
114
+ Kuhsaft::Brick.new.tap do |b|
115
+ b.type = Kuhsaft::BrickType.new
116
+ end
117
+ end
118
+
119
+ let! :brick_list do
120
+ p = create(:page)
121
+ p.bricks << brick
122
+ p.save
123
+ p
124
+ end
125
+
126
+ it 'updates fulltext on bricklist after saving a single brick' do
127
+ brick.brick_list.should_receive(:update_fulltext)
128
+ brick.brick_list.should_receive(:save!)
129
+ brick.text = 'foobar'
130
+ brick.save
131
+ end
132
+ end
133
+ end
110
134
  end
@@ -21,6 +21,10 @@ describe Kuhsaft::Page do
21
21
  it 'should only find published results' do
22
22
  Kuhsaft::Page.search('English Title').should be_all { |p| p.published? == true }
23
23
  end
24
+
25
+ it 'should find by using the old api' do
26
+ Kuhsaft::Page.search('English').should == Kuhsaft::Page.search('English')
27
+ end
24
28
  end
25
29
 
26
30
  describe '.position_of' do
@@ -72,6 +76,15 @@ describe Kuhsaft::Page do
72
76
  end
73
77
  end
74
78
 
79
+ describe '#content_page' do
80
+ it 'returns only content pages ("" or nil)' do
81
+ p1, p2, p3 = 3.times.map { create(:page) }
82
+ p2.update_attribute :page_type, Kuhsaft::PageType::REDIRECT
83
+ p3.update_attribute :page_type, nil
84
+ Kuhsaft::Page.content_page.should == [p1, p3]
85
+ end
86
+ end
87
+
75
88
  describe "#state_class" do
76
89
 
77
90
  let(:page) { Kuhsaft::Page.new }
@@ -259,6 +272,13 @@ describe Kuhsaft::Page do
259
272
  page.link.should eq('/en/news')
260
273
  end
261
274
  end
275
+
276
+ context 'when url part is empty' do
277
+ it 'strips the trailing slash' do
278
+ page = create(:page, :page_type => Kuhsaft::PageType::NAVIGATION)
279
+ page.link.should eq('/en')
280
+ end
281
+ end
262
282
  end
263
283
 
264
284
  describe '#navigation?' do
@@ -303,18 +323,6 @@ describe Kuhsaft::Page do
303
323
  page.save
304
324
  end
305
325
 
306
- it 'contains the title' do
307
- page.fulltext.should include('my title')
308
- end
309
-
310
- it 'contains the keywords' do
311
- page.fulltext.should include('key words')
312
- end
313
-
314
- it 'contains the description' do
315
- page.fulltext.should include('descrip tion')
316
- end
317
-
318
326
  it 'contains the page part content' do
319
327
  page.fulltext.should include('oh la la')
320
328
  end
@@ -333,4 +341,56 @@ describe Kuhsaft::Page do
333
341
  page.url.should be_present
334
342
  end
335
343
  end
344
+
345
+ describe '#url_without_locale' do
346
+ let :page do
347
+ create(:page, :slug => 'page')
348
+ end
349
+
350
+ context 'without parent' do
351
+ it 'returns url without leading /' do
352
+ page.url_without_locale.should_not start_with '/'
353
+ end
354
+
355
+ it 'returns a single slug' do
356
+ page.url_without_locale.should == 'page'
357
+ end
358
+ end
359
+
360
+ context 'when parent is navigation' do
361
+ let :parent do
362
+ create(:page, :page_type => Kuhsaft::PageType::NAVIGATION)
363
+ end
364
+
365
+ let :child do
366
+ create(:page, :slug => 'child', :parent => parent)
367
+ end
368
+
369
+ it 'returns url without leading /' do
370
+ child.url_without_locale.should_not start_with '/'
371
+ end
372
+
373
+ it 'does not concatenate the parent slug' do
374
+ child.url_without_locale.should == 'child'
375
+ end
376
+ end
377
+
378
+ context 'when parent is normal page' do
379
+ let :parent do
380
+ create(:page, :slug => 'parent')
381
+ end
382
+
383
+ let :child do
384
+ create(:page, :slug => 'child', :parent => parent)
385
+ end
386
+
387
+ it 'returns url without leading /' do
388
+ child.url_without_locale.should_not start_with '/'
389
+ end
390
+
391
+ it 'does not concatenate the parent slug' do
392
+ child.url_without_locale.should == 'parent/child'
393
+ end
394
+ end
395
+ end
336
396
  end
@@ -1,7 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Kuhsaft::TextBrick do
4
-
5
4
  let :text_brick do
6
5
  Kuhsaft::TextBrick.new
7
6
  end
@@ -17,4 +16,15 @@ describe Kuhsaft::TextBrick do
17
16
  text_brick.user_can_add_childs?.should be_false
18
17
  end
19
18
  end
19
+
20
+ describe '#collect_fulltext' do
21
+ before do
22
+ text_brick.text = '<div><b>foo</b> <b>bar</b></div>'
23
+ text_brick.read_more_text = '<div><span>foo</span><span>bar</span></div>'
24
+ end
25
+
26
+ it 'sanitizes text and read_more_text' do
27
+ text_brick.collect_fulltext.should == 'foo bar foobar'
28
+ end
29
+ end
20
30
  end
@@ -0,0 +1,36 @@
1
+ class ActionView::TestCase::TestController
2
+ def default_url_options(options={})
3
+ { :locale => I18n.locale }.merge options
4
+ end
5
+ end
6
+
7
+ # The following snippet breaks url_for
8
+ # as described by franca
9
+ # https://github.com/screenconcept/shoestrap/issues/23
10
+ # <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
11
+ #class ActionDispatch::Routing::RouteSet
12
+ #def default_url_options(options={})
13
+ #{ :locale => I18n.locale }
14
+ #end
15
+ #end
16
+ # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
17
+
18
+ # Fixes the missing default locale problem in controller specs
19
+ # See http://www.ruby-forum.com/topic/3448797#1041659
20
+ class ActionController::TestCase
21
+ module Behavior
22
+ def process_with_default_locale(action, parameters = nil, session = nil, flash = nil, http_method = 'GET')
23
+ parameters = { :locale => I18n.locale }.merge( parameters || {} )
24
+ process_without_default_locale(action, parameters, session, flash, http_method)
25
+ end
26
+ alias_method_chain :process, :default_locale
27
+ end
28
+ end
29
+
30
+ module ActionDispatch::Assertions::RoutingAssertions
31
+ def assert_recognizes_with_default_locale(expected_options, path, extras = {}, message=nil)
32
+ expected_options = { :locale => I18n.locale.to_s }.merge(expected_options || {} )
33
+ assert_recognizes_without_default_locale(expected_options, path, extras, message)
34
+ end
35
+ alias_method_chain :assert_recognizes, :default_locale
36
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kuhsaft
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.0
4
+ version: 1.7.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -13,7 +13,7 @@ authors:
13
13
  autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
- date: 2013-05-27 00:00:00.000000000 Z
16
+ date: 2013-05-31 00:00:00.000000000 Z
17
17
  dependencies:
18
18
  - !ruby/object:Gem::Dependency
19
19
  name: rspec
@@ -319,6 +319,38 @@ dependencies:
319
319
  - - '='
320
320
  - !ruby/object:Gem::Version
321
321
  version: 4.0.2
322
+ - !ruby/object:Gem::Dependency
323
+ name: pg_search
324
+ requirement: !ruby/object:Gem::Requirement
325
+ none: false
326
+ requirements:
327
+ - - ! '>='
328
+ - !ruby/object:Gem::Version
329
+ version: '0'
330
+ type: :runtime
331
+ prerelease: false
332
+ version_requirements: !ruby/object:Gem::Requirement
333
+ none: false
334
+ requirements:
335
+ - - ! '>='
336
+ - !ruby/object:Gem::Version
337
+ version: '0'
338
+ - !ruby/object:Gem::Dependency
339
+ name: htmlentities
340
+ requirement: !ruby/object:Gem::Requirement
341
+ none: false
342
+ requirements:
343
+ - - ! '>='
344
+ - !ruby/object:Gem::Version
345
+ version: '0'
346
+ type: :runtime
347
+ prerelease: false
348
+ version_requirements: !ruby/object:Gem::Requirement
349
+ none: false
350
+ requirements:
351
+ - - ! '>='
352
+ - !ruby/object:Gem::Version
353
+ version: '0'
322
354
  description: Kuhsaft is a Rails engine that offers a simple CMS.
323
355
  email: developers@screenconcept.ch
324
356
  executables: []
@@ -402,9 +434,13 @@ files:
402
434
  - app/views/kuhsaft/image_bricks/image_brick/_edit.html.haml
403
435
  - app/views/kuhsaft/link_bricks/_link_brick.html.haml
404
436
  - app/views/kuhsaft/link_bricks/link_brick/_edit.html.haml
437
+ - app/views/kuhsaft/pages/index.html.haml
405
438
  - app/views/kuhsaft/pages/show.html.haml
406
439
  - app/views/kuhsaft/placeholder_bricks/_placeholder_brick.html.haml
407
440
  - app/views/kuhsaft/placeholder_bricks/placeholder_brick/_edit.html.haml
441
+ - app/views/kuhsaft/search/_form.html.haml
442
+ - app/views/kuhsaft/search/_results.html.haml
443
+ - app/views/kuhsaft/search/_results_entry.html.haml
408
444
  - app/views/kuhsaft/slider_bricks/_slider_brick.html.haml
409
445
  - app/views/kuhsaft/slider_bricks/slider_brick/_edit.html.haml
410
446
  - app/views/kuhsaft/text_bricks/_text_brick.html.haml
@@ -438,6 +474,8 @@ files:
438
474
  - config/locales/views/kuhsaft/cms/bricks/de.yml
439
475
  - config/locales/views/kuhsaft/cms/pages/de.yml
440
476
  - config/locales/views/kuhsaft/cms/video_bricks/de.yml
477
+ - config/locales/views/kuhsaft/search/de.yml
478
+ - config/locales/views/kuhsaft/search/en.yml
441
479
  - config/locales/views/kuhsaft/text_brick/de.yml
442
480
  - config/locales/views/layouts/de.yml
443
481
  - config/routes.rb
@@ -452,6 +490,7 @@ files:
452
490
  - db/migrate/09_add_additional_fields_to_kuhsaft_bricks.rb
453
491
  - db/migrate/10_add_redirect_url_to_kuhsaft_pages.rb
454
492
  - db/migrate/11_update_url_and_redirect_url_value.rb
493
+ - db/migrate/12_regenerate_fulltext.rb
455
494
  - db/seeds.rb
456
495
  - lib/generators/kuhsaft/assets/install_generator.rb
457
496
  - lib/generators/kuhsaft/translations/add_generator.rb
@@ -459,6 +498,7 @@ files:
459
498
  - lib/kuhsaft/engine.rb
460
499
  - lib/kuhsaft/orderable.rb
461
500
  - lib/kuhsaft/partial_extractor.rb
501
+ - lib/kuhsaft/searchable.rb
462
502
  - lib/kuhsaft/translatable.rb
463
503
  - lib/kuhsaft/version.rb
464
504
  - lib/kuhsaft.rb
@@ -505,10 +545,13 @@ files:
505
545
  - spec/dummy/script/rails
506
546
  - spec/factories.rb
507
547
  - spec/features/cms_pages_spec.rb
548
+ - spec/features/search_spec.rb
508
549
  - spec/helpers/kuhsaft/cms/admin_helper_spec.rb
550
+ - spec/helpers/kuhsaft/pages_helper_spec.rb
509
551
  - spec/kuhsaft_spec.rb
510
552
  - spec/lib/brick_list_spec.rb
511
553
  - spec/lib/engine_spec.rb
554
+ - spec/lib/searchable_spec.rb
512
555
  - spec/lib/translatable_spec.rb
513
556
  - spec/models/accordion_brick_spec.rb
514
557
  - spec/models/accordion_item_brick_spec.rb
@@ -529,6 +572,7 @@ files:
529
572
  - spec/models/two_column_brick_spec.rb
530
573
  - spec/models/video_brick_spec.rb
531
574
  - spec/spec_helper.rb
575
+ - spec/support/default_url_options.rb
532
576
  - spec/support/kuhsaft_spec_helper.rb
533
577
  homepage: http://github.com/screenconcept/kuhsaft
534
578
  licenses: []
@@ -544,7 +588,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
544
588
  version: '0'
545
589
  segments:
546
590
  - 0
547
- hash: 1502563843709159469
591
+ hash: 2803199904668343026
548
592
  required_rubygems_version: !ruby/object:Gem::Requirement
549
593
  none: false
550
594
  requirements:
@@ -553,7 +597,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
553
597
  version: '0'
554
598
  segments:
555
599
  - 0
556
- hash: 1502563843709159469
600
+ hash: 2803199904668343026
557
601
  requirements: []
558
602
  rubyforge_project: kuhsaft
559
603
  rubygems_version: 1.8.24
@@ -597,10 +641,13 @@ test_files:
597
641
  - spec/dummy/script/rails
598
642
  - spec/factories.rb
599
643
  - spec/features/cms_pages_spec.rb
644
+ - spec/features/search_spec.rb
600
645
  - spec/helpers/kuhsaft/cms/admin_helper_spec.rb
646
+ - spec/helpers/kuhsaft/pages_helper_spec.rb
601
647
  - spec/kuhsaft_spec.rb
602
648
  - spec/lib/brick_list_spec.rb
603
649
  - spec/lib/engine_spec.rb
650
+ - spec/lib/searchable_spec.rb
604
651
  - spec/lib/translatable_spec.rb
605
652
  - spec/models/accordion_brick_spec.rb
606
653
  - spec/models/accordion_item_brick_spec.rb
@@ -621,4 +668,5 @@ test_files:
621
668
  - spec/models/two_column_brick_spec.rb
622
669
  - spec/models/video_brick_spec.rb
623
670
  - spec/spec_helper.rb
671
+ - spec/support/default_url_options.rb
624
672
  - spec/support/kuhsaft_spec_helper.rb