diff --git a/app/models/web.rb b/app/models/web.rb index c9571738..938d05bc 100644 --- a/app/models/web.rb +++ b/app/models/web.rb @@ -1,48 +1,74 @@ class Web < ActiveRecord::Base - has_many :pages, :dependent => :destroy + ## Associations + + has_many :pages, :dependent => :destroy has_many :wiki_files, :dependent => :destroy + has_many :revisions, :through => :pages + + ## Hooks + + before_save :sanitize_markup + after_save :create_files_directory + + before_validation :validate_address + + ## Validations + + validates_uniqueness_of :address + + validates_length_of :color, :in => 3..6 + + ## Methods + + # @return [Wiki] a new Wiki instance def wiki Wiki.new end - + def settings_changed?(markup, safe_mode, brackets_only) self.markup != markup || self.safe_mode != safe_mode || self.brackets_only != brackets_only end - + def add_page(name, content, time, author, renderer) - page = page(name) || Page.new(:web => self, :name => name) + page = page(name) || pages.build(:name => name) page.revise(content, name, time, author, renderer) end - + + # @return [Array] a collection of all the names of the authors that + # have ever contributed to the pages for this Web def authors - connection.select_all( - 'SELECT DISTINCT r.author AS author ' + - 'FROM revisions r ' + - 'JOIN pages p ON p.id = r.page_id ' + - 'WHERE p.web_id = ' + self.id.to_s + - ' ORDER by 1 ' - ).collect { |row| row['author'] } + revisions.all( + :select => "DISTINCT revisions.author", + :order => "1" + ).collect(&:author) end def categories select.map { |page| page.categories }.flatten.uniq.sort end + # @param [String] name the name of some associated Page record to find + # @return [Page, nil] the associated Page record, or +nil+ if no record is + # found with the provided name def page(name) - pages.first(:conditions => ['name = ?', name]) - end - - def last_page - return Page.first(:order => 'id desc', :conditions => ['web_id = ?', self.id]) + pages.find_by_name(name) end - def has_page?(name) - Page.count(:conditions => ['web_id = ? AND name = ?', id, name]) > 0 + # @return [Page] the last associated Page record + def last_page + pages.last end - + + # @param [String] name the name of some potential Page record + # @return [Boolean] whether or not a given Page record exists with a given + # name + def has_page?(name) + pages.exists?(:name => name) + end + def has_redirect_for?(name) WikiReference.page_that_redirects_for(self, name) end @@ -52,11 +78,11 @@ class Web < ActiveRecord::Base end def has_file?(file_name) - WikiFile.find_by_file_name(file_name) != nil + wiki_files.exists?(:file_name => file_name) end - - def file_list(sort_order = 'file_name') - WikiFile.all(:order => sort_order, :conditions => ['web_id = ?', id]) + + def file_list(sort_order="file_name") + wiki_files.all(:order => sort_order) end def pages_that_link_to(page_name) @@ -67,68 +93,83 @@ class Web < ActiveRecord::Base WikiReference.pages_that_link_to_file(self, file_name) end + # @param [String] file_name the name of some WikiFile of interest + # @return [String, nil] the description of some WikiFile of interest, nil if + # the WikiFile could not be found def description(file_name) - file = WikiFile.find_by_file_name(file_name) - file.description if file + wiki_files.find_by_file_name(file_name).try(:description) end + # @return [Symbol] the type of markup used by this Web def markup - read_attribute('markup').to_sym + self[:markup].to_sym end + # @return [Hash] a Hash wherein the key is some author's name, and the + # values are an array of page names for that author. def page_names_by_author - connection.select_all( - 'SELECT DISTINCT r.author AS author, p.name AS page_name ' + - 'FROM revisions r ' + - 'JOIN pages p ON r.page_id = p.id ' + - "WHERE p.web_id = #{self.id} " + - 'ORDER by p.name' - ).inject({}) { |result, row| - author, page_name = row['author'], row['page_name'] - result[author] = [] unless result.has_key?(author) - result[author] << page_name - result - } - end + data = revisions.all( + :select => "DISTINCT revisions.author AS author, pages.name AS page_name", + :order => "pages.name" + ) - def remove_pages(pages_to_be_removed) - pages_to_be_removed.each { |p| p.destroy } - end - - def revised_at - select.most_recent_revision - end - - def select(&condition) - PageSet.new(self, pages, condition) - end - - def select_all - PageSet.new(self, pages, nil) - end - - def to_param - address - end - - def create_files_directory - return unless allow_uploads == 1 - dummy_file = self.wiki_files.build(:file_name => '0', :description => '0', :content => '0') - File.umask(0002) - dir = File.dirname(dummy_file.content_path) - begin - require 'fileutils' - FileUtils.mkdir_p dir - dummy_file.save - dummy_file.destroy - rescue => e - logger.error("Failed create files directory for #{self.address}: #{e}") - raise "Instiki could not create directory to store uploaded files. " + - "Please make sure that Instiki is allowed to create directory " + - "#{File.expand_path(dir)} and add files to it." + data.inject({}) do |result, revision| + result[revision.author] ||= [] + result[revision.author] << revision.page_name + result end end + # OPTIMIZE Use the +delete_all+ with conditions for extra efficiency + def remove_pages(pages_to_be_removed) + pages_to_be_removed.each { |p| p.destroy } + end + + def revised_at + select.most_recent_revision + end + + def select(&condition) + PageSet.new(self, pages, condition) + end + + def select_all + PageSet.new(self, pages, nil) + end + + # @return [String] uses the +address+ attribute for this record's parameter name + def to_param + address + end + + # Called by an +after_save+ hook. Creates the directory that houses this + # Web's associated files. + # + # TODO Move this into the WikiFile model + def create_files_directory + return unless allow_uploads == 1 + + dummy_file = wiki_files.build( + :file_name => "0", + :description => "0", + :content => "0" + ) + + File.umask(0002) + + begin + dummy_file.content_path.parent.mkpath + dummy_file.save + dummy_file.destroy + rescue => e + logger.error "Failed create files directory for #{address}: #{e}" + raise "Instiki could not create directory to store uploaded files. " + + "Please make sure that Instiki is allowed to create directory " + + "#{dummy_file.content_path.expand_path} and add files to it." + end + end + + # @return [Pathname] the path to the files for this record def files_path path = Rails.root.join("webs") if default_web? @@ -138,6 +179,7 @@ class Web < ActiveRecord::Base end end + # @return [Pathname] the path to PNGs for this record def blahtex_pngs_path files_path.join("pngs") end @@ -148,31 +190,27 @@ class Web < ActiveRecord::Base def wiki_words pages.inject([]) { |wiki_words, page| wiki_words << page.wiki_words }.flatten.uniq end - + # Returns an array of all the page names on this web def page_names pages.map { |p| p.name } end - + protected - before_save :sanitize_markup - after_save :create_files_directory - before_validation :validate_address - validates_uniqueness_of :address - validates_length_of :color, :in => 3..6 - + def sanitize_markup self.markup = markup.to_s end - + def validate_address unless address == CGI.escape(address) self.errors.add(:address, 'should contain only valid URI characters') raise Instiki::ValidationError.new("#{self.class.human_attribute_name('address')} #{errors.on(:address)}") end end - + + # @return [Boolean] whether or not this record is considered the default Web def default_web? - defined? DEFAULT_WEB and self.address == DEFAULT_WEB + defined?(DEFAULT_WEB) && address == DEFAULT_WEB end end diff --git a/test/unit/web_test.rb b/test/unit/web_test.rb index ec85f81c..75bcc836 100644 --- a/test/unit/web_test.rb +++ b/test/unit/web_test.rb @@ -1,5 +1,4 @@ require File.dirname(__FILE__) + '/../test_helper' -#require File.expand_path(File.dirname(__FILE__) + '/../test_helper') class WebTest < ActiveSupport::TestCase fixtures :system, :webs, :pages, :revisions, :wiki_references