diff options
-rw-r--r-- | Gemfile | 1 | ||||
-rw-r--r-- | Gemfile.lock | 1 | ||||
-rw-r--r-- | app/controllers/admin_public_body_controller.rb | 5 | ||||
-rw-r--r-- | app/controllers/public_body_controller.rb | 124 | ||||
-rw-r--r-- | config/general.yml-example | 5 | ||||
-rw-r--r-- | doc/THEMES.md | 2 | ||||
-rw-r--r-- | lib/configuration.rb | 1 | ||||
-rw-r--r-- | lib/tasks/gettext.rake | 42 | ||||
-rw-r--r-- | public/stylesheets/admin.css | 77 | ||||
-rw-r--r-- | public/stylesheets/alt.css | 232 | ||||
-rwxr-xr-x | script/generate_pot.sh | 22 | ||||
-rw-r--r-- | spec/controllers/public_body_controller_spec.rb | 74 | ||||
-rw-r--r-- | spec/models/xapian_spec.rb | 31 | ||||
-rw-r--r-- | vendor/plugins/acts_as_xapian/lib/acts_as_xapian.rb | 43 |
14 files changed, 247 insertions, 413 deletions
@@ -55,6 +55,7 @@ group :test do gem 'fakeweb' gem 'coveralls', :require => false gem 'webrat' + gem 'nokogiri' end group :development do diff --git a/Gemfile.lock b/Gemfile.lock index 8d5059d7e..15c5245ff 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -270,6 +270,7 @@ DEPENDENCIES net-http-local net-purge newrelic_rpm + nokogiri pg rack rails (= 3.1.12) diff --git a/app/controllers/admin_public_body_controller.rb b/app/controllers/admin_public_body_controller.rb index ec2a08dbc..e0da234b0 100644 --- a/app/controllers/admin_public_body_controller.rb +++ b/app/controllers/admin_public_body_controller.rb @@ -14,6 +14,7 @@ class AdminPublicBodyController < AdminController def _lookup_query_internal @locale = self.locale_from_params() + underscore_locale = @locale.gsub '-', '_' I18n.with_locale(@locale) do @query = params[:query] if @query == "" @@ -23,10 +24,10 @@ class AdminPublicBodyController < AdminController if @page == "" @page = nil end - @public_bodies = PublicBody.joins(:translations).where(@query.nil? ? "public_body_translations.locale = '#{@locale}'" : + @public_bodies = PublicBody.joins(:translations).where(@query.nil? ? "public_body_translations.locale = '#{underscore_locale}'" : ["(lower(public_body_translations.name) like lower('%'||?||'%') or lower(public_body_translations.short_name) like lower('%'||?||'%') or - lower(public_body_translations.request_email) like lower('%'||?||'%' )) AND (public_body_translations.locale = '#{@locale}')", @query, @query, @query]).paginate :order => "public_body_translations.name", :page => @page, :per_page => 100 + lower(public_body_translations.request_email) like lower('%'||?||'%' )) AND (public_body_translations.locale = '#{underscore_locale}')", @query, @query, @query]).paginate :order => "public_body_translations.name", :page => @page, :per_page => 100 end @public_bodies_by_tag = PublicBody.find_by_tag(@query) end diff --git a/app/controllers/public_body_controller.rb b/app/controllers/public_body_controller.rb index 9f692c5ba..02f0ceb19 100644 --- a/app/controllers/public_body_controller.rb +++ b/app/controllers/public_body_controller.rb @@ -86,34 +86,45 @@ class PublicBodyController < ApplicationController def list long_cache # XXX move some of these tag SQL queries into has_tag_string.rb - @query = "%#{params[:public_body_query].nil? ? "" : params[:public_body_query]}%" + + like_query = params[:public_body_query] + like_query = "" if like_query.nil? + like_query = "%#{like_query}%" + @tag = params[:tag] - @locale = self.locale_from_params() - default_locale = I18n.default_locale.to_s - locale_condition = "(upper(public_body_translations.name) LIKE upper(?) - OR upper(public_body_translations.notes) LIKE upper (?)) - AND public_body_translations.locale = ? - AND public_bodies.id <> #{PublicBody.internal_admin_body.id}" + + @locale = self.locale_from_params + underscore_locale = @locale.gsub '-', '_' + underscore_default_locale = I18n.default_locale.to_s.gsub '-', '_' + + where_condition = "public_bodies.id <> #{PublicBody.internal_admin_body.id}" + where_parameters = [] + + first_letter = false + + base_tag_condition = " AND (SELECT count(*) FROM has_tag_string_tags" \ + " WHERE has_tag_string_tags.model_id = public_bodies.id" \ + " AND has_tag_string_tags.model = 'PublicBody'" + + # Restrict the public bodies shown according to the tag + # parameter supplied in the URL: if @tag.nil? or @tag == "all" @tag = "all" - conditions = [locale_condition, @query, @query, default_locale] elsif @tag == 'other' category_list = PublicBodyCategories::get().tags().map{|c| "'"+c+"'"}.join(",") - conditions = [locale_condition + ' AND (select count(*) from has_tag_string_tags where has_tag_string_tags.model_id = public_bodies.id - and has_tag_string_tags.model = \'PublicBody\' - and has_tag_string_tags.name in (' + category_list + ')) = 0', @query, @query, default_locale] + where_condition += base_tag_condition + " AND has_tag_string_tags.name in (#{category_list})) = 0" elsif @tag.size == 1 @tag.upcase! - conditions = [locale_condition + ' AND public_body_translations.first_letter = ?', @query, @query, default_locale, @tag] + # The first letter queries have to be done on + # translations, so just indicate to add that later: + first_letter = true elsif @tag.include?(":") name, value = HasTagString::HasTagStringTag.split_tag_into_name_value(@tag) - conditions = [locale_condition + ' AND (select count(*) from has_tag_string_tags where has_tag_string_tags.model_id = public_bodies.id - and has_tag_string_tags.model = \'PublicBody\' - and has_tag_string_tags.name = ? and has_tag_string_tags.value = ?) > 0', @query, @query, default_locale, name, value] + where_condition += base_tag_condition + " AND has_tag_string_tags.name = ? AND has_tag_string_tags.value = ?) > 0" + where_parameters.concat [name, value] else - conditions = [locale_condition + ' AND (select count(*) from has_tag_string_tags where has_tag_string_tags.model_id = public_bodies.id - and has_tag_string_tags.model = \'PublicBody\' - and has_tag_string_tags.name = ?) > 0', @query, @query, default_locale, @tag] + where_condition += base_tag_condition + " AND has_tag_string_tags.name = ?) > 0" + where_parameters.concat [@tag] end if @tag == "all" @@ -128,10 +139,45 @@ class PublicBodyController < ApplicationController @description = _("in the category ‘{{category_name}}’", :category_name=>category_name) end end + I18n.with_locale(@locale) do - @public_bodies = PublicBody.where(conditions).joins(:translations).order("public_body_translations.name").paginate( - :page => params[:page], :per_page => 100 - ) + + if AlaveteliConfiguration::public_body_list_fallback_to_default_locale + # Unfortunately, when we might fall back to the + # default locale, this is a rather complex query: + query = %Q{ + SELECT public_bodies.*, COALESCE(current_locale.name, default_locale.name) AS display_name + FROM public_bodies + LEFT OUTER JOIN public_body_translations as current_locale + ON (public_bodies.id = current_locale.public_body_id + AND current_locale.locale = ? AND #{get_public_body_list_translated_condition 'current_locale', first_letter}) + LEFT OUTER JOIN public_body_translations as default_locale + ON (public_bodies.id = default_locale.public_body_id + AND default_locale.locale = ? AND #{get_public_body_list_translated_condition 'default_locale', first_letter}) + WHERE #{where_condition} AND COALESCE(current_locale.name, default_locale.name) IS NOT NULL + ORDER BY display_name} + sql = [query, underscore_locale, like_query, like_query] + sql.push @tag if first_letter + sql += [underscore_default_locale, like_query, like_query] + sql.push @tag if first_letter + sql += where_parameters + @public_bodies = PublicBody.paginate_by_sql( + sql, + :page => params[:page], + :per_page => 100) + else + # The simpler case where we're just searching in the current locale: + where_condition = get_public_body_list_translated_condition('public_body_translations', first_letter, true) + + ' AND ' + where_condition + where_sql = [where_condition, like_query, like_query] + where_sql.push @tag if first_letter + where_sql += [underscore_locale] + where_parameters + @public_bodies = PublicBody.where(where_sql) \ + .joins(:translations) \ + .order("public_body_translations.name") \ + .paginate(:page => params[:page], :per_page => 100) + end + respond_to do |format| format.html { render :template => "public_body/list" } end @@ -165,27 +211,27 @@ class PublicBodyController < ApplicationController [[total_column, [{ - :title => 'Public bodies with the most requests', - :y_axis => 'Number of requests', + :title => _('Public bodies with the most requests'), + :y_axis => _('Number of requests'), :highest => true}]], ['info_requests_successful_count', [{ - :title => 'Public bodies with the most successful requests', - :y_axis => 'Percentage of total requests', + :title => _('Public bodies with the most successful requests'), + :y_axis => _('Percentage of total requests'), :highest => true}, { - :title => 'Public bodies with the fewest successful requests', - :y_axis => 'Percentage of total requests', + :title => _('Public bodies with the fewest successful requests'), + :y_axis => _('Percentage of total requests'), :highest => false}]], ['info_requests_overdue_count', [{ - :title => 'Public bodies with most overdue requests', - :y_axis => 'Percentage of requests that are overdue', + :title => _('Public bodies with most overdue requests'), + :y_axis => _('Percentage of requests that are overdue'), :highest => true}]], ['info_requests_not_held_count', [{ - :title => 'Public bodies that most frequently replied with "Not Held"', - :y_axis => 'Percentage of total requests', + :title => _('Public bodies that most frequently replied with "Not Held"'), + :y_axis => _('Percentage of total requests'), :highest => true}]]].each do |column, graphs_properties| graphs_properties.each do |graph_properties| @@ -207,7 +253,7 @@ class PublicBodyController < ApplicationController data_to_draw = { 'id' => "#{column}-#{highest ? 'highest' : 'lowest'}", - 'x_axis' => 'Public Bodies', + 'x_axis' => _('Public Bodies'), 'y_axis' => graph_properties[:y_axis], 'errorbars' => percentages, 'title' => graph_properties[:title]} @@ -236,4 +282,18 @@ class PublicBodyController < ApplicationController @xapian_requests = perform_search_typeahead(query, PublicBody) render :partial => "public_body/search_ahead" end + + private + def get_public_body_list_translated_condition(table, first_letter=false, locale=nil) + result = "(upper(#{table}.name) LIKE upper(?)" \ + " OR upper(#{table}.notes) LIKE upper (?))" + if first_letter + result += " AND #{table}.first_letter = ?" + end + if locale + result += " AND #{table}.locale = ?" + end + result + end + end diff --git a/config/general.yml-example b/config/general.yml-example index 37b0c2fc9..5f3697a36 100644 --- a/config/general.yml-example +++ b/config/general.yml-example @@ -200,3 +200,8 @@ PUBLIC_BODY_STATISTICS_PAGE: false # The page of statistics for public bodies will only consider public # bodies that have had at least this number of requests: MINIMUM_REQUESTS_FOR_STATISTICS: 50 + +# If only some of the public bodies have been translated into every +# available locale, you can allow a fallback to the default locale for +# listing of public bodies. +PUBLIC_BODY_LIST_FALLBACK_TO_DEFAULT_LOCALE: false diff --git a/doc/THEMES.md b/doc/THEMES.md index bae7d7665..d6109cdc5 100644 --- a/doc/THEMES.md +++ b/doc/THEMES.md @@ -64,7 +64,7 @@ add custom help pages, as described below. # Branding the site The core templates that comprise the layout and user interface of an -Alaveteli site live in `app/views/`. They are use Rails' ERB syntax. +Alaveteli site live in `app/views/`. They use Rails' ERB syntax. For example, the template for the home page lives at `app/views/general/frontpage.html.erb`, and the template for the "about us" page is at `app/views/help/about.html.erb`. diff --git a/lib/configuration.rb b/lib/configuration.rb index d6fd8765f..ab985c8bf 100644 --- a/lib/configuration.rb +++ b/lib/configuration.rb @@ -49,6 +49,7 @@ module AlaveteliConfiguration :NEW_RESPONSE_REMINDER_AFTER_DAYS => [3, 10, 24], :OVERRIDE_ALL_PUBLIC_BODY_REQUEST_EMAILS => '', :PUBLIC_BODY_STATISTICS_PAGE => false, + :PUBLIC_BODY_LIST_FALLBACK_TO_DEFAULT_LOCALE => false, :RAW_EMAILS_LOCATION => 'files/raw_emails', :READ_ONLY => '', :RECAPTCHA_PRIVATE_KEY => 'x', diff --git a/lib/tasks/gettext.rake b/lib/tasks/gettext.rake index ace7205ae..366dfbe88 100644 --- a/lib/tasks/gettext.rake +++ b/lib/tasks/gettext.rake @@ -9,27 +9,31 @@ namespace :gettext do end end - desc "Update pot file only, without fuzzy guesses (these are done by Transifex)" - task :findpot => :environment do + desc "Update pot/po files for a theme." + task :find_theme => :environment do + theme = ENV['THEME'] + unless theme + puts "Usage: Specify an Alaveteli-theme with THEME=[theme directory name]" + exit(0) + end load_gettext - $LOAD_PATH << File.join(File.dirname(__FILE__),'..','..','lib') - require 'gettext_i18n_rails/haml_parser' - files = files_to_translate + msgmerge = Rails.application.config.gettext_i18n_rails.msgmerge + msgmerge ||= %w[--sort-output --no-location --no-wrap] + GetText.update_pofiles_org( + text_domain, + theme_files_to_translate(theme), + "version 0.0.1", + :po_root => theme_locale_path(theme), + :msgmerge => msgmerge + ) + end - #write found messages to tmp.pot - temp_pot = "tmp.pot" - GetText::rgettext(files, temp_pot) + def theme_files_to_translate(theme) + Dir.glob("{vendor/plugins/#{theme}/lib}/**/*.{rb,erb}") + end - #merge tmp.pot and existing pot - FileUtils.mkdir_p('locale') - GetText::msgmerge("locale/app.pot", temp_pot, "alaveteli", :po_root => 'locale', :msgmerge=>[ :no_wrap, :sort_output ]) - Dir.glob("locale/*/app.po") do |po_file| - GetText::msgmerge(po_file, temp_pot, "alaveteli", :po_root => 'locale', :msgmerge=>[ :no_wrap, :sort_output ]) - end - File.delete(temp_pot) - end + def theme_locale_path(theme) + File.join(Rails.root, "vendor", "plugins", theme, "locale-theme") + end - def files_to_translate - Dir.glob("{app,lib,config,locale}/**/*.{rb,erb,haml,rhtml}") - end end diff --git a/public/stylesheets/admin.css b/public/stylesheets/admin.css deleted file mode 100644 index 7f8d646d3..000000000 --- a/public/stylesheets/admin.css +++ /dev/null @@ -1,77 +0,0 @@ -/* FOIFA admin CSS */ - -h2 { - margin-top: 0.2em; - margin-bottom: 0.5em; -} - -table { - border-collapse: collapse; - margin-bottom: 1em; -} - -td, th { - border: solid 1px #000000; -} -td { - vertical-align: top -} -td { - max-width: 30em; - overflow: auto; -} - -tr.odd { - background-color: #bbbbbb; -} - -tr.even { - background-color: #dddddd; -} - -.entry_changed { - font-weight: bold; -} - -form { - margin-bottom: 1em; -} - -#notice { - color: green; - border: solid 1px green; - padding: 1em; -} -#error, #errorExplanation { - color: red; - border: solid 1px red; - padding: 1em; -} -#tag_help { - float: right; - width: 25%; - margin-top: 0; -} -#public_body_form { - float: left; - width: 70%; -} - -.forms_on_one_line { - display: inline; -} - -.user_photo_on_admin { - float: right; -} - -.user_photo_on_admin img { - width: 96px; - height: 96px; - vertical-align: middle; - border: 1px solid #dddddd; - margin-right: 5px; - padding: 2px; -} - - diff --git a/public/stylesheets/alt.css b/public/stylesheets/alt.css deleted file mode 100644 index bf6ba2d16..000000000 --- a/public/stylesheets/alt.css +++ /dev/null @@ -1,232 +0,0 @@ -/*------------------------------------------------ global */ -body -{ - padding: 0px; - margin: 0px; - text-align: center; - font-family: Tahoma, Geneva, sans-serif; -} - -/*------------------------------------------------ banner */ - -#banner -{ - position: relative; - top: 0px; - left: 0px; - width: 100%; - height: 200px; - margin: 0px; - background-color: #F0F0F0; - background-image: url(../images/navimg/bnnr-ifyoudontask.png); - background-repeat: no-repeat; - background-position: center top; - border-color: #993233; - border-width: 0 0 3px 0; - border-style: solid; -} - -/*------------------------------------------------ header; not needed? */ -#header -{ display: none; } - -/*------------------------------------------------ search */ - -#navigation_search -{ - position: absolute; - width: 100%; - left: 0px; - top: 175px; - z-index: 150; - text-align: right; - -moz-opacity: 0.7!important; - filter: alpha(opacity= 70)!important; - opacity: 0.7!important; -} - #navigation_search input - { - border-color: #010101; - border-width: 1px; - border-style: solid; - background-color: #fff; - color: #000; - } - #navigation_search input#navigation_search_query - { - width: 8em; - } - -#navigation_search p { margin: 0 0.6em 0 0; } - -/*------------------------------------------------ topnav */ -#topnav -{ - position: relative; - top: 0px; - left: 0px; - width: 100%; - height: auto; - overflow: auto; - padding: 0px 0px 0px 0px; - z-index: 100; - background-color: #000; - font-size: 0.9em; -} - - #topnav ul - { - list-style: none; - margin: 0px; - padding: 0px; - } - - #topnav li - { - float: left; - } - - #topnav li a, #topnav li a:visited - { - display: block; - margin: 0px; - padding: 0.15em 0.6em 0.25em 0.8em; - color: #ADADAD; - text-decoration: none; - } - - #topnav li a:hover - { color: #fff; } - - #topnav li a:active { } - - #topnav li a.on, #topnav li a.on:visited - { - font-weight: bold; - color: #000; - } - - #topnav li a.on:hover {} - - #topnav li a.on:active {} - -/*-------------------------- login/signup */ -#logged_in_bar -{ - float: right; - clear: none; - font-size: 0.9em; - z-index: 200; - padding: 0.20em 10px 0.25em 1em; - color: #FFF; -} - #logged_in_bar a, #logged_in_bar a:visited { color: #92B3FF; } - -/*------------------------------------------------ temp stuff */ -#staging, #alpha_notice -{ display: none;} - -#staging -{ - border-color: #FF201D; - border-width: 1px; - border-style: dotted; - clear: both; - text-align: center; - color: #FF201D; - margin-top: 40px; -} - -#alpha_notice -{ - padding: 0 20px 0 20px; - border-color: #FF201D; - border-width: 1px; - border-style: dotted; - background-color: #f0f0f0; -} - -/*------------------------------------------------ wrapper*/ - -#wrapper -{ - position: relative; - clear: both; - top: 0em; - padding: 0px; - margin: 0px auto 1.2em auto; - max-width: 50em; - /* = 800px at default size? so 1em = 16px*/ - _width: 50em; - text-align: left; -} - -/*------------------------------------------------ content */ - -#content -{ - position: relative; - padding: 2em 1em 1em 1em; -} - -h1, h2, h3 -{ - font-family: Trebuchet, Trebuchet MS, Helvetica, sans-serif; - /*Arial Black, Gadget, sans-serif*/ - font-weight: bold; - line-height: 1em; - letter-spacing: 0em; - color: #555; -} - h1 { font-size: 1.8em;} - h2 { font-size: 1.4em;} - h3 { font-size: 1em;} - -h4, h5, h6 -{} - - -/*---------------- content : recent requests sidebar */ -#find_information -{ - width: 10.5em; - float: right; - clear: none; - background-color: #e0e0e0; - background-image: url(../images/navimg/sidebag-bg-wide.png); - background-repeat: no-repeat; - background-position: center top; - padding: 105px 0.25em 0.25em 0.35em; -} - #find_information h1 - { font-size: 1.2em; line-height: 0.85em; margin-top: 0px;} - - #find_information p - { font-size: 0.8em; line-height: 1em; } - - #find_information input#query - { width: 12em; } - -/*---------------- content : find body */ -#make_requests -{ - float: left; - clear: none; - width: 34em; - text-align: center; -} - -/*------------------------------------------------ footer */ - -#footer -{ - clear: both; - position: relative; - background-color: #F0F0F0; - border-color: #993233; - border-width: 3px 0 0 0; - border-style: solid; - margin: 1em 0 0 0; - padding: 0.5em 0 0.5em 0; - font-size: 0.85em; -}
\ No newline at end of file diff --git a/script/generate_pot.sh b/script/generate_pot.sh deleted file mode 100755 index c0540c3d9..000000000 --- a/script/generate_pot.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash - -echo "This is NOT a completed script! Just use it as reference for what to do from the command line, or fix it until it works!" -exit 1 - -cd `dirname $0` -# grab latest po files from Transifex -tx pull -a -f -git status | grep app.po | awk '{print $3}' | xargs git add -git commit -m "Backup latest po files from Transifex" - -# now regenerate POT and PO files from Alaveteli source -bundle exec rake gettext:store_model_attributes -bundle exec rake gettext:findpot - -# upload the result to Transifex -tx push -t - -# re-download (it removes the fuzzy strings and normalises it to the format last committed) -tx pull -a -f -git status | grep app.po | awk '{print $3}' | xargs git add -git commit -m "Updated POT" diff --git a/spec/controllers/public_body_controller_spec.rb b/spec/controllers/public_body_controller_spec.rb index 92130f3d0..2d1b1466f 100644 --- a/spec/controllers/public_body_controller_spec.rb +++ b/spec/controllers/public_body_controller_spec.rb @@ -1,6 +1,8 @@ # coding: utf-8 require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') +require 'nokogiri' + describe PublicBodyController, "when showing a body" do render_views @@ -78,24 +80,80 @@ describe PublicBodyController, "when listing bodies" do response.should be_success end - it "should list all bodies from default locale, even when there are no translations for selected locale" do - I18n.with_locale(:en) do - @english_only = PublicBody.new(:name => 'English only', - :short_name => 'EO', - :request_email => 'english@flourish.org', - :last_edit_editor => 'test', - :last_edit_comment => '') - @english_only.save + def make_single_language_example(locale) + result = nil + I18n.with_locale(locale) do + case locale + when :en + result = PublicBody.new(:name => 'English only', + :short_name => 'EO') + when :es + result = PublicBody.new(:name => 'Español Solamente', + :short_name => 'ES') + else + raise StandardError.new "Unknown locale #{locale}" + end + result.request_email = "#{locale}@example.org" + result.last_edit_editor = 'test' + result.last_edit_comment = '' + result.save end + result + end + + it "with no fallback, should only return bodies from the current locale" do + @english_only = make_single_language_example :en + @spanish_only = make_single_language_example :es + get :list, {:locale => 'es'} + assigns[:public_bodies].include?(@english_only).should == false + assigns[:public_bodies].include?(@spanish_only).should == true + end + + it "if fallback is requested, should list all bodies from default locale, even when there are no translations for selected locale" do + AlaveteliConfiguration.stub!(:public_body_list_fallback_to_default_locale).and_return(true) + @english_only = make_single_language_example :en get :list, {:locale => 'es'} assigns[:public_bodies].include?(@english_only).should == true end + it 'if fallback is requested, should still list public bodies only with translations in the current locale' do + AlaveteliConfiguration.stub!(:public_body_list_fallback_to_default_locale).and_return(true) + @spanish_only = make_single_language_example :es + get :list, {:locale => 'es'} + assigns[:public_bodies].include?(@spanish_only).should == true + end + + it "if fallback is requested, make sure that there are no duplicates listed" do + AlaveteliConfiguration.stub!(:public_body_list_fallback_to_default_locale).and_return(true) + get :list, {:locale => 'es'} + pb_ids = assigns[:public_bodies].map { |pb| pb.id } + unique_pb_ids = pb_ids.uniq + pb_ids.sort.should === unique_pb_ids.sort + end + it 'should show public body names in the selected locale language if present' do get :list, {:locale => 'es'} response.should contain('El Department for Humpadinking') end + it 'should not show the internal admin authority' do + PublicBody.internal_admin_body + get :list, {:locale => 'en'} + response.should_not contain('Internal admin authority') + end + + it 'should order on the translated name, even with the fallback' do + # The names of each public body is in: + # <span class="head"><a>Public Body Name</a></span> + # ... eo extract all of those, and check that they are ordered: + AlaveteliConfiguration.stub!(:public_body_list_fallback_to_default_locale).and_return(true) + get :list, {:locale => 'es'} + parsed = Nokogiri::HTML(response.body) + public_body_names = parsed.xpath '//span[@class="head"]/a/text()' + public_body_names = public_body_names.map { |pb| pb.to_s } + public_body_names.should == public_body_names.sort + end + it 'should show public body names in the selected locale language if present for a locale with underscores' do AlaveteliLocalization.set_locales('he_IL en', 'en') get :list, {:locale => 'he_IL'} diff --git a/spec/models/xapian_spec.rb b/spec/models/xapian_spec.rb index 7aab9cdc6..c7c21e3a0 100644 --- a/spec/models/xapian_spec.rb +++ b/spec/models/xapian_spec.rb @@ -400,3 +400,34 @@ describe ActsAsXapian::Search, "#words_to_highlight" do end end + +describe InfoRequestEvent, " when faced with a race condition during xapian_mark_needs_index" do + + before(:each) do + load_raw_emails_data + get_fixtures_xapian_index + # Use the before create job hook to simulate a race condition with another process + # by creating an acts_as_xapian_job record for the same model + class InfoRequestEvent + def xapian_before_create_job_hook(action, model, model_id) + ActsAsXapian::ActsAsXapianJob.create!(:model => model, + :model_id => model_id, + :action => action) + end + end + end + + after(:each) do + # Reset the before create job hook + class InfoRequestEvent + def xapian_before_create_job_hook(action, model, model_id) + end + end + end + + it 'should not raise an error but should fail silently' do + ir = info_requests(:naughty_chicken_request) + ir.reindex_request_events + end + +end diff --git a/vendor/plugins/acts_as_xapian/lib/acts_as_xapian.rb b/vendor/plugins/acts_as_xapian/lib/acts_as_xapian.rb index f2cd1075c..fcf7a778d 100644 --- a/vendor/plugins/acts_as_xapian/lib/acts_as_xapian.rb +++ b/vendor/plugins/acts_as_xapian/lib/acts_as_xapian.rb @@ -913,16 +913,11 @@ module ActsAsXapian # Used to mark changes needed by batch indexer def xapian_mark_needs_index - model = self.class.base_class.to_s - model_id = self.id - ActiveRecord::Base.transaction do - found = ActsAsXapianJob.delete_all([ "model = ? and model_id = ?", model, model_id]) - job = ActsAsXapianJob.new - job.model = model - job.model_id = model_id - job.action = 'update' - job.save! - end + xapian_create_job('update', self.class.base_class.to_s, self.id) + end + + def xapian_mark_needs_destroy + xapian_create_job('destroy', self.class.base_class.to_s, self.id) end # Allow reindexing to be skipped if a flag is set @@ -931,18 +926,26 @@ module ActsAsXapian xapian_mark_needs_index end - def xapian_mark_needs_destroy - model = self.class.base_class.to_s - model_id = self.id - ActiveRecord::Base.transaction do - found = ActsAsXapianJob.delete_all([ "model = ? and model_id = ?", model, model_id]) - job = ActsAsXapianJob.new - job.model = model - job.model_id = model_id - job.action = 'destroy' - job.save! + def xapian_create_job(action, model, model_id) + begin + ActiveRecord::Base.transaction do + ActsAsXapianJob.delete_all([ "model = ? and model_id = ?", model, model_id]) + xapian_before_create_job_hook(action, model, model_id) + ActsAsXapianJob.create!(:model => model, + :model_id => model_id, + :action => action) + end + rescue ActiveRecord::RecordNotUnique => e + # Given the error handling in ActsAsXapian::update_index, we can just fail silently if + # another process has inserted an acts_as_xapian_jobs record for this model. + raise unless (e.message =~ /duplicate key value violates unique constraint "index_acts_as_xapian_jobs_on_model_and_model_id"/) end end + + # A hook method that can be used in tests to simulate e.g. an external process inserting a record + def xapian_before_create_job_hook(action, model, model_id) + end + end ###################################################################### |