diff options
26 files changed, 305 insertions, 155 deletions
diff --git a/app/controllers/admin_request_controller.rb b/app/controllers/admin_request_controller.rb index 7e6c365c1..d5bd4c4d6 100644 --- a/app/controllers/admin_request_controller.rb +++ b/app/controllers/admin_request_controller.rb @@ -46,6 +46,7 @@ class AdminRequestController < AdminController old_awaiting_description = @info_request.awaiting_description old_allow_new_responses_from = @info_request.allow_new_responses_from old_handle_rejected_responses = @info_request.handle_rejected_responses + old_tag_string = @info_request.tag_string @info_request.title = params[:info_request][:title] @info_request.prominence = params[:info_request][:prominence] @@ -55,6 +56,7 @@ class AdminRequestController < AdminController @info_request.awaiting_description = params[:info_request][:awaiting_description] == "true" ? true : false @info_request.allow_new_responses_from = params[:info_request][:allow_new_responses_from] @info_request.handle_rejected_responses = params[:info_request][:handle_rejected_responses] + @info_request.tag_string = params[:info_request][:tag_string] if @info_request.valid? @info_request.save! @@ -65,7 +67,8 @@ class AdminRequestController < AdminController :old_described_state => old_described_state, :described_state => @info_request.described_state, :old_awaiting_description => old_awaiting_description, :awaiting_description => @info_request.awaiting_description, :old_allow_new_responses_from => old_allow_new_responses_from, :allow_new_responses_from => @info_request.allow_new_responses_from, - :old_handle_rejected_responses => old_handle_rejected_responses, :handle_rejected_responses => @info_request.handle_rejected_responses + :old_handle_rejected_responses => old_handle_rejected_responses, :handle_rejected_responses => @info_request.handle_rejected_responses, + :old_tag_string => old_tag_string, :tag_string => @info_request.tag_string }) flash[:notice] = 'Request successfully updated.' redirect_to request_admin_url(@info_request) diff --git a/app/controllers/public_body_controller.rb b/app/controllers/public_body_controller.rb index 5f51948ee..560206900 100644 --- a/app/controllers/public_body_controller.rb +++ b/app/controllers/public_body_controller.rb @@ -65,24 +65,28 @@ class PublicBodyController < ApplicationController end def list + # XXX move some of these tag SQL queries into has_tag_string.rb @tag = params[:tag] if @tag.nil? @tag = "all" conditions = [] elsif @tag == 'other' category_list = PublicBodyCategories::CATEGORIES.map{|c| "'"+c+"'"}.join(",") - conditions = ['(select count(*) from public_body_tags where public_body_tags.public_body_id = public_bodies.id - and public_body_tags.name in (' + category_list + ')) = 0'] + conditions = ['(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'] elsif @tag.size == 1 @tag.upcase! conditions = ['first_letter = ?', @tag] elsif @tag.include?(":") - name, value = PublicBodyTag.split_tag_into_name_value(@tag) - conditions = ['(select count(*) from public_body_tags where public_body_tags.public_body_id = public_bodies.id - and public_body_tags.name = ? and public_body_tags.value = ?) > 0', name, value] + name, value = HasTagString::HasTagStringTag.split_tag_into_name_value(@tag) + conditions = ['(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', name, value] else - conditions = ['(select count(*) from public_body_tags where public_body_tags.public_body_id = public_bodies.id - and public_body_tags.name = ?) > 0', @tag] + conditions = ['(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', @tag] end @public_bodies = PublicBody.paginate( :order => "public_bodies.name", :page => params[:page], :per_page => 1000, # fit all councils on one page diff --git a/app/controllers/request_controller.rb b/app/controllers/request_controller.rb index e9475e1b2..734c649dd 100644 --- a/app/controllers/request_controller.rb +++ b/app/controllers/request_controller.rb @@ -180,7 +180,7 @@ class RequestController < ApplicationController # First time we get to the page, just display it if params[:submitted_new_request].nil? || params[:reedit] # Read parameters in - public body must be passed in - params[:info_request] = { :public_body_id => params[:public_body_id] } if !params[:info_request] + params[:info_request] = { :public_body_id => params[:public_body_id], :tag_string => params[:tags] } if !params[:info_request] if !params[:info_request][:public_body_id] redirect_to frontpage_url return diff --git a/app/models/info_request.rb b/app/models/info_request.rb index 4f997baa6..3f5224907 100644 --- a/app/models/info_request.rb +++ b/app/models/info_request.rb @@ -50,6 +50,8 @@ class InfoRequest < ActiveRecord::Base has_many :censor_rules, :order => 'created_at desc' has_many :exim_logs, :order => 'exim_log_done_id' + has_tag_string + # user described state (also update in info_request_event, admin_request/edit.rhtml) validates_inclusion_of :described_state, :in => [ 'waiting_response', diff --git a/app/models/public_body.rb b/app/models/public_body.rb index d54c761e6..1bd9dcc94 100644 --- a/app/models/public_body.rb +++ b/app/models/public_body.rb @@ -40,9 +40,10 @@ class PublicBody < ActiveRecord::Base validates_uniqueness_of :name has_many :info_requests, :order => 'created_at desc' - has_many :public_body_tags has_many :track_things, :order => 'created_at desc' + has_tag_string + # like find_by_url_name but also search historic url_name if none found def self.find_by_url_name_with_historic(name) found = PublicBody.find_all_by_url_name(name) @@ -183,71 +184,12 @@ class PublicBody < ActiveRecord::Base end end - # Given an input string of tags, sets all tags to that string. - # XXX This immediately saves the new tags. - def tag_string=(tag_string) - tag_string = tag_string.strip - # split tags apart - tags = tag_string.split(/\s+/).uniq - - ActiveRecord::Base.transaction do - for public_body_tag in self.public_body_tags - public_body_tag.destroy - end - self.public_body_tags = [] - for tag in tags - # see if is a machine tags (i.e. a tag which has a value) - name, value = PublicBodyTag.split_tag_into_name_value(tag) - - public_body_tag = PublicBodyTag.new(:name => name, :value => value) - self.public_body_tags << public_body_tag - public_body_tag.public_body = self - end - end - end - def tag_string - return self.public_body_tags.map { |t| t.name_and_value }.join(' ') - end - def has_tag?(tag) - for public_body_tag in self.public_body_tags - if public_body_tag.name == tag - return true - end - end - return false - end - class TagNotFound < StandardError - end - def get_tag_values(tag) - found = false - results = [] - for public_body_tag in self.public_body_tags - if public_body_tag.name == tag - found = true - if !public_body_tag.value.nil? - results << public_body_tag.value - end - end - end - if !found - raise TagNotFound - end - return results - end - def add_tag_if_not_already_present(tag) - self.tag_string = self.tag_string + " " + tag - end - - # Find all public bodies with a particular tag - def self.find_by_tag(tag) - return PublicBodyTag.find(:all, :conditions => ['name = ?', tag] ).map { |t| t.public_body }.sort { |a,b| a.name <=> b.name } - end # Use tags to describe what type of thing this is def type_of_authority(html = false) types = [] first = true - for tag in self.public_body_tags + for tag in self.tags if PublicBodyCategories::CATEGORIES_BY_TAG.include?(tag.name) desc = PublicBodyCategories::CATEGORY_SINGULAR_BY_TAG[tag.name] if first diff --git a/app/models/public_body_tag.rb b/app/models/public_body_tag.rb deleted file mode 100644 index e24ace7d7..000000000 --- a/app/models/public_body_tag.rb +++ /dev/null @@ -1,48 +0,0 @@ -# == Schema Information -# Schema version: 92 -# -# Table name: public_body_tags -# -# id :integer not null, primary key -# public_body_id :integer not null -# name :text not null -# created_at :datetime not null -# value :text -# - -# models/public_body_tag.rb: -# Categories for public bodies. -# -# Copyright (c) 2008 UK Citizens Online Democracy. All rights reserved. -# Email: francis@mysociety.org; WWW: http://www.mysociety.org/ -# -# $Id: public_body_tag.rb,v 1.29 2009-09-17 21:10:05 francis Exp $ - -class PublicBodyTag < ActiveRecord::Base - strip_attributes! - - validates_presence_of :public_body - validates_presence_of :name - - belongs_to :public_body - - def name_and_value - ret = self.name - if !self.value.nil? - ret += ":" + self.value - end - return ret - end - - def PublicBodyTag.split_tag_into_name_value(tag) - sections = tag.split(/:/) - name = sections[0] - if sections[1] - value = sections[1,sections.size].join(":") - else - value = nil - end - return name, value - end -end - diff --git a/app/views/admin_public_body/_tags.rhtml b/app/views/admin_public_body/_tags.rhtml index 362424013..85dc942fd 100644 --- a/app/views/admin_public_body/_tags.rhtml +++ b/app/views/admin_public_body/_tags.rhtml @@ -1,4 +1,4 @@ -<% for t in body.public_body_tags %> +<% for t in body.tags %> <% if t.value %> <%= link_to(h(t.name), main_url(list_public_bodies_url(:tag => t.name, :only_path => true))) %>:<%= link_to(h(t.value), main_url(list_public_bodies_url(:tag => t.name_and_value, :only_path => true))) %> <% else %> diff --git a/app/views/admin_request/_tags.rhtml b/app/views/admin_request/_tags.rhtml new file mode 100644 index 000000000..22fbf13c8 --- /dev/null +++ b/app/views/admin_request/_tags.rhtml @@ -0,0 +1,8 @@ +<% for t in info_request.tags %> + <% if t.value %> + <%=h t.name %>:<%=h t.value %> + <% else %> + <%=h t.name %> + <% end %> +<% end %> + diff --git a/app/views/admin_request/edit.rhtml b/app/views/admin_request/edit.rhtml index 1bfe4cb90..b659c676d 100644 --- a/app/views/admin_request/edit.rhtml +++ b/app/views/admin_request/edit.rhtml @@ -41,6 +41,9 @@ <br/>(don't forget to change 'awaiting description' when you set described state)<br/> </p> + <p><label for="info_request_tag_string"><strong>Tags</strong> <small>(space separated, can use key:value)</small></label><br/> + <%= text_field 'info_request', 'tag_string', :size => 60 %></p> + <p><%= submit_tag 'Save changes', :accesskey => 's' %> </p> diff --git a/app/views/admin_request/show.rhtml b/app/views/admin_request/show.rhtml index 279d09295..aac68ad2e 100644 --- a/app/views/admin_request/show.rhtml +++ b/app/views/admin_request/show.rhtml @@ -43,6 +43,7 @@ </span> <br> <strong>Incoming email address:</strong> <%= link_to h(@info_request.incoming_email), "mailto:" + @info_request.incoming_email %> <br> +<b>Tags:</b> <%= render :partial => 'tags', :locals => { :info_request => @info_request} %> <br> </p> <% end %> diff --git a/app/views/request/_sidebar.rhtml b/app/views/request/_sidebar.rhtml index 4a503d574..5557f0a6b 100644 --- a/app/views/request/_sidebar.rhtml +++ b/app/views/request/_sidebar.rhtml @@ -4,6 +4,10 @@ <h2>Act on what you've learnt</h2> <div class="act_link"> + <%= link_to '<img src="/images/helpmeinvestigate.png" alt="" class="rss">', "http://helpmeinvestigate.com/"%> + <%= link_to 'Get help investigating', "http://helpmeinvestigate.com/"%> + </div> + <div class="act_link"> <%= link_to '<img src="/images/writetothem.png" alt="" class="rss">', "http://www.writetothem.com"%> <%= link_to 'Write to your politician', "http://www.writetothem.com"%> </div> @@ -11,15 +15,16 @@ <%= link_to '<img src="/images/pledgebank.png" alt="" class="rss">', "http://www.pledgebank.com"%> <%= link_to 'Pledge with others', "http://www.pledgebank.com"%> </div> - <div class="act_link"> + <!-- <div class="act_link"> <%= link_to '<img src="/images/petitions.png" alt="" class="rss">', "http://petitions.number10.gov.uk"%> <%= link_to 'Petition the PM', "http://petitions.number10.gov.uk"%> - </div> + </div> --> <div class="act_link"> <%= link_to '<img src="/images/wordpress.png" alt="" class="rss">', "http://wordpress.com/"%> <%= link_to 'Start your own blog', "http://wordpress.com/"%> </div> + <% view_cache :ttl => 1.day, :tag => ['similar', @info_request.id] do %> <% if !@xapian_similar.nil? && @xapian_similar.results.size > 0 %> <h2>Similar requests</h2> @@ -34,6 +39,6 @@ <% end %> <p><%= link_to "Event history details", request_details_url(@info_request) %></p> - <p><a href="/help/about#commercial">Are you the owner of - any commercial copyright on this page?</a></p> + <p><small><a href="/help/about#commercial">Are you the owner of + any commercial copyright on this page?</a></small></p> </div> diff --git a/app/views/request/new.rhtml b/app/views/request/new.rhtml index b48966e2f..1b061fd46 100644 --- a/app/views/request/new.rhtml +++ b/app/views/request/new.rhtml @@ -115,8 +115,8 @@ <%= o.text_area :body, :rows => 20, :cols => 60 %> </p> <% end %> - - <div class="form_button"> + + <div class="form_button"> <script type="text/javascript">document.write('<input name="doSpell" type="button" value="Check spelling" onClick="openSpellChecker(document.getElementById(\'write_form\').body);"/> (optional)')</script> </div> @@ -147,7 +147,18 @@ <%= hidden_field_tag(:preview, 1 ) %> <%= submit_tag "Preview your public request" %> </div> - </div> + + <% if !@info_request.tag_string.empty? %> + <p class="form_note"> + <!-- <label class="form_label" for="info_request_tag_string">Tags:</label> + <%= f.text_field :tag_string, :size => 50 %> --> + + <%= f.hidden_field(:tag_string) %> + <strong>Tags:</strong> <%=h @info_request.tag_string %> + </p> + <% end %> + + </div> <% end %> diff --git a/app/views/request/preview.rhtml b/app/views/request/preview.rhtml index 427d036d9..97b92ffae 100644 --- a/app/views/request/preview.rhtml +++ b/app/views/request/preview.rhtml @@ -34,12 +34,17 @@ <p> <%= f.hidden_field(:title) %> <%= f.hidden_field(:public_body_id, { :value => @info_request.public_body_id } ) %> + <%= f.hidden_field(:tag_string) %> <%= hidden_field_tag(:submitted_new_request, 1) %> <%= hidden_field_tag(:preview, 0 ) %> <%= submit_tag "Re-edit this request", :name => 'reedit' %> <%= submit_tag "Send public " + h(@info_request.law_used_full) + " request", :name => 'submit' %> </p> + <% if !@info_request.tag_string.empty? %> + <p><strong>Tags:</strong> <%=h @info_request.tag_string %></p> + <% end %> + <% end %> diff --git a/config/environment.rb b/config/environment.rb index 5a61d7e56..c4351dbd1 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -105,7 +105,7 @@ if (MySociety::Config.get("DOMAIN", "") != "") } end -# Load monkey patches from lib/ +# Load monkey patches and other things from lib/ require 'tmail_extensions.rb' require 'activesupport_cache_extensions.rb' require 'public_body_categories.rb' diff --git a/db/migrate/093_move_to_has_tag_string.rb b/db/migrate/093_move_to_has_tag_string.rb new file mode 100644 index 000000000..58f36c224 --- /dev/null +++ b/db/migrate/093_move_to_has_tag_string.rb @@ -0,0 +1,23 @@ +class MoveToHasTagString < ActiveRecord::Migration + def self.up + rename_table :public_body_tags, :has_tag_string_tags + + # we rename existing column: + rename_column :has_tag_string_tags, :public_body_id, :model_id + # if using has_tag_string afresh in another project, can use this: + # add_column :has_tag_string_tags, :model_id, :integer, :null => false + + # the model needs a default value, so build in stages: + add_column :has_tag_string_tags, :model, :string + HasTagString::HasTagStringTag.update_all "model = 'PublicBody'" + change_column :has_tag_string_tags, :model, :string, :null => false + # just use this for a fresh project: + # add_column :has_tag_string_tags, :model, :string, :null => false + + add_index :has_tag_string_tags, [:model, :model_id] + end + + def self.down + raise "no reverse migration" + end +end diff --git a/db/migrate/094_remove_old_tags_foreign_key.rb b/db/migrate/094_remove_old_tags_foreign_key.rb new file mode 100644 index 000000000..1a87b97c2 --- /dev/null +++ b/db/migrate/094_remove_old_tags_foreign_key.rb @@ -0,0 +1,17 @@ +class RemoveOldTagsForeignKey < ActiveRecord::Migration + def self.up + if ActiveRecord::Base.connection.adapter_name == "PostgreSQL" + execute "ALTER TABLE has_tag_string_tags DROP CONSTRAINT fk_public_body_tags_public_body" + end + + remove_index :public_body_tags, [:public_body_id, :name, :value] + remove_index :public_body_tags, :name + + add_index :has_tag_string_tags, [:model, :model_id, :name, :value] + add_index :has_tag_string_tags, :name + end + + def self.down + raise "no reverse migration" + end +end diff --git a/db/schema.rb b/db/schema.rb index 0c90be6b1..cf117c4e3 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -9,7 +9,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 92) do +ActiveRecord::Schema.define(:version => 94) do create_table "acts_as_xapian_jobs", :force => true do |t| t.string "model", :null => false @@ -65,6 +65,18 @@ ActiveRecord::Schema.define(:version => 92) do add_index "exim_logs", ["exim_log_done_id"], :name => "index_exim_logs_on_exim_log_done_id" + create_table "has_tag_string_tags", :force => true do |t| + t.integer "model_id", :null => false + t.text "name", :null => false + t.datetime "created_at", :null => false + t.text "value" + t.string "model", :null => false + end + + add_index "has_tag_string_tags", ["model", "model_id", "name", "value"], :name => "index_has_tag_string_tags_on_model_and_model_id_and_name_and_va" + add_index "has_tag_string_tags", ["model", "model_id"], :name => "index_has_tag_string_tags_on_model_and_model_id" + add_index "has_tag_string_tags", ["name"], :name => "index_has_tag_string_tags_on_name" + create_table "holidays", :force => true do |t| t.date "day" t.text "description" @@ -173,16 +185,6 @@ ActiveRecord::Schema.define(:version => 92) do add_index "public_bodies", ["first_letter"], :name => "index_public_bodies_on_first_letter" add_index "public_bodies", ["url_name"], :name => "index_public_bodies_on_url_name", :unique => true - create_table "public_body_tags", :force => true do |t| - t.integer "public_body_id", :null => false - t.text "name", :null => false - t.datetime "created_at", :null => false - t.text "value" - end - - add_index "public_body_tags", ["name", "public_body_id", "value"], :name => "index_public_body_tags_on_public_body_id_and_name_and_value", :unique => true - add_index "public_body_tags", ["name"], :name => "index_public_body_tags_on_name" - create_table "public_body_versions", :force => true do |t| t.integer "public_body_id" t.integer "version" diff --git a/public/images/helpmeinvestigate.png b/public/images/helpmeinvestigate.png Binary files differnew file mode 100644 index 000000000..e9aaf5aec --- /dev/null +++ b/public/images/helpmeinvestigate.png diff --git a/public/stylesheets/main.css b/public/stylesheets/main.css index 95824846f..727a8e1d2 100644 --- a/public/stylesheets/main.css +++ b/public/stylesheets/main.css @@ -1065,7 +1065,6 @@ form.feed_form_main { } div.act_link img { - background-color: red; border: none; vertical-align: middle; text-decoration: none; diff --git a/spec/models/has_tag_string_tag_spec.rb b/spec/models/has_tag_string_tag_spec.rb new file mode 100644 index 000000000..9dfcf3daf --- /dev/null +++ b/spec/models/has_tag_string_tag_spec.rb @@ -0,0 +1,15 @@ +require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') + +describe HasTagString::HasTagStringTag, " when fiddling with tag strings " do + fixtures :public_bodies + + it "should be able to make a new tag and save it" do + @tag = HasTagString::HasTagStringTag.new + @tag.model = 'PublicBody' + @tag.model_id = public_bodies(:geraldine_public_body).id + @tag.name = "moo" + @tag.save + end + +end + diff --git a/spec/models/public_body_spec.rb b/spec/models/public_body_spec.rb index ff4a78bdf..f00138ab3 100644 --- a/spec/models/public_body_spec.rb +++ b/spec/models/public_body_spec.rb @@ -94,6 +94,27 @@ describe PublicBody, " using machine tags" do end end +describe PublicBody, "when finding_by_tags" do + fixtures :public_bodies + + before do + @geraldine = public_bodies(:geraldine_public_body) + @geraldine.tag_string = 'rabbit' + @humpadink = public_bodies(:humpadink_public_body) + @humpadink.tag_string = 'coney:5678 coney:1234' + end + + it 'should be able to find bodies by string' do + found = PublicBody.find_by_tag('rabbit') + found.should == [ @geraldine ] + end + + it 'should be able to find when there are multiple tags in one body, without returning duplicates' do + found = PublicBody.find_by_tag('coney') + found.should == [ @humpadink ] + end +end + describe PublicBody, " when making up the URL name" do before do @public_body = PublicBody.new diff --git a/spec/models/public_body_tag_spec.rb b/spec/models/public_body_tag_spec.rb deleted file mode 100644 index 09a597bd0..000000000 --- a/spec/models/public_body_tag_spec.rb +++ /dev/null @@ -1,14 +0,0 @@ -require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') - -describe PublicBodyTag, " when fiddling with public body tags " do - fixtures :public_bodies - - it "should be able to make a new tag and save it" do - @tag = PublicBodyTag.new - @tag.public_body = public_bodies(:geraldine_public_body) - @tag.name = "moo" - @tag.save - end - -end - @@ -1,8 +1,9 @@ +destroy request + + Next (things that will reduce admin time mainly) ==== -Remove Petitions sidebar link, add one to HelpMeInvestigate. - Richard says he wants the internationalisation to be so it could be one site with combined search. Why obey the notion of a country? I'm not sure, but it might be prudent to write it so it can run multiple jurisdictions in @@ -345,6 +346,9 @@ Failed to detect attachments are emails and decode them: When indexing .docx do you need to index docProps/custom.xml and docProps/app.xml as well as word/document.xml ? (thread on xapian-discuss does so) +Consider using odt2txt or unoconv +http://www-verimag.imag.fr/~moy/opendocument/ + Mime type / extension wrong on these .docx's http://www.whatdotheyknow.com/request/bridleway_classifications http://www.whatdotheyknow.com/request/19976/response/51468/attach/3/TU%20MembershipTeachers%20SolidarityTU%20231009.docx.doc (thinks it is doc when it is docx) diff --git a/vendor/plugins/has_tag_string/README.txt b/vendor/plugins/has_tag_string/README.txt new file mode 100644 index 000000000..0d3a38229 --- /dev/null +++ b/vendor/plugins/has_tag_string/README.txt @@ -0,0 +1 @@ +Plugin used only in WhatDoTheyKnow right now. diff --git a/vendor/plugins/has_tag_string/init.rb b/vendor/plugins/has_tag_string/init.rb new file mode 100644 index 000000000..4a07073a7 --- /dev/null +++ b/vendor/plugins/has_tag_string/init.rb @@ -0,0 +1,2 @@ +require 'has_tag_string' + diff --git a/vendor/plugins/has_tag_string/lib/has_tag_string.rb b/vendor/plugins/has_tag_string/lib/has_tag_string.rb new file mode 100644 index 000000000..fdcc35a55 --- /dev/null +++ b/vendor/plugins/has_tag_string/lib/has_tag_string.rb @@ -0,0 +1,144 @@ +# lib/has_tag_string.rb: +# Lets a model have tags, represented as space separate strings in a public +# interface, but stored in the database as keys. Each tag can have a value +# followed by a colon - e.g. url:http://www.flourish.org +# +# Copyright (c) 2010 UK Citizens Online Democracy. All rights reserved. +# Email: francis@mysociety.org; WWW: http://www.mysociety.org/ + +module HasTagString + # Represents one tag of one model. + # The migration to make this is currently only in WDTK code. + class HasTagStringTag < ActiveRecord::Base + # XXX strip_attributes! + + validates_presence_of :name + + # Return instance of the model that this tag tags + def tagged_model + return self.model.constantize.find(self.model_id) + end + + # For display purposes, returns the name and value as a:b, or + # if there is no value just the name a + def name_and_value + ret = self.name + if !self.value.nil? + ret += ":" + self.value + end + return ret + end + + # Parses a text version of one single tag, such as "a:b" and returns + # the name and value, with nil for value if there isn't one. + def HasTagStringTag.split_tag_into_name_value(tag) + sections = tag.split(/:/) + name = sections[0] + if sections[1] + value = sections[1,sections.size].join(":") + else + value = nil + end + return name, value + end + end + + # Methods which are added to the model instances being tagged + module InstanceMethods + # Given an input string of tags, sets all tags to that string. + # XXX This immediately saves the new tags. + def tag_string=(tag_string) + if tag_string.nil? + tag_string = "" + end + + tag_string = tag_string.strip + # split tags apart + tags = tag_string.split(/\s+/).uniq + + ActiveRecord::Base.transaction do + for tag in self.tags + tag.destroy + end + self.tags = [] + for tag in tags + # see if is a machine tags (i.e. a tag which has a value) + name, value = HasTagStringTag.split_tag_into_name_value(tag) + + tag = HasTagStringTag.new( + :model => self.class.base_class.to_s, + :model_id => self.id, + :name => name, :value => value + ) + self.tags << tag + end + end + end + + # Returns the tags the model has, as a space separated string + def tag_string + return self.tags.map { |t| t.name_and_value }.join(' ') + end + + # Test to see if class is tagged with the given tag + def has_tag?(tag_as_string) + for tag in self.tags + if tag.name == tag_as_string + return true + end + end + return false + end + + class TagNotFound < StandardError + end + + # If the tag is a machine tag, returns array of its values + def get_tag_values(tag_as_string) + found = false + results = [] + for tag in self.tags + if tag.name == tag_as_string + found = true + if !tag.value.nil? + results << tag.value + end + end + end + if !found + raise TagNotFound + end + return results + end + + # Adds a new tag to the model, if it isn't already there + def add_tag_if_not_already_present(tag_as_string) + self.tag_string = self.tag_string + " " + tag_as_string + end + end + + # Methods which are added to the model class being tagged + module ClassMethods + # Find all public bodies with a particular tag + def find_by_tag(tag_as_string) + return HasTagStringTag.find(:all, :conditions => + ['name = ? and model = ?', tag_as_string, self.to_s ] + ).map { |t| t.tagged_model }.sort { |a,b| a.name <=> b.name }.uniq + end + end + + ###################################################################### + # Main entry point, add has_tag_string to your model. + module HasMethods + def has_tag_string() + has_many :tags, :conditions => "model = '" + self.to_s + "'", :foreign_key => "model_id", :class_name => 'HasTagString::HasTagStringTag' + + include InstanceMethods + self.class.send :include, ClassMethods + end + end + +end + +ActiveRecord::Base.extend HasTagString::HasMethods + |