#-- # Copyright (C) 2008 Dimitrij Denissenko # Please read LICENSE document for more information. #++ module Acts module Previewable @@previewable_class_names = [] mattr_accessor :previewable_class_names def self.included(base) base.extend(ClassMethods) end def self.previewable_classes previewable_class_names.map(&:constantize) end def self.register(klass) self.previewable_class_names = (self.previewable_class_names + [klass.name]).uniq.sort end module ClassMethods def define_previewable(options = {}) unless previewable? cattr_accessor :previewable_options self.previewable_options = options unless Acts::Previewable.previewable_class_names.include?(self.name) Acts::Previewable.register(self) end include InstanceMethods end end def previewable? self.included_modules.include?(InstanceMethods) end end module InstanceMethods def self.included(base) base.extend(GeneralSingletonMethods) if base.previewable_options[:searchable_columns] base.extend(SearchSingletonMethods) end end def previewable @previewable ||= Acts::Previewable::InstanceBase.new(self) end end module GeneralSingletonMethods def previewable @previewable ||= Acts::Previewable::ClassBase.new(self) end end module SearchSingletonMethods def token_search(pattern, projects = Project.current) tokenizer = Retrospectiva::SearchTokenizer.new(pattern) if tokenizer.tokens.empty? # Empty search yields 0 results [] else find_all_by_tokens_in_projects(tokenizer, projects) end end def find_all_by_tokens_in_projects(tokenizer, projects = Project.current) options = previewable.find_options || {} options[:limit] ||= 10 options[:order] ||= "#{table_name}.#{previewable.date_column} DESC" options[:conditions] = tokenizer.to_a(*previewable.searchable_columns) scope = previewable.project_scope(projects) with_scope(:find => scope) { find(:all, options) } end end class InstanceBase attr_reader :base, :title, :content, :permalink_options attr_accessor :target_project def initialize(base) previewable_options = base.class.previewable_options option = previewable_options[:title] @title = if option.is_a?(Proc) option.call(base) else base.send(option) end option = previewable_options[:content] @content = if option.is_a?(Proc) [option.call(base), 0] elsif option.is_a?(Array) [base.send(option[0]), option[1]] else [base.send(option), 0] end @base = base end def date base.send(base.class.previewable.date_column) end def permalink_options options = base.class.previewable.url if base.class.previewable.url_params.is_a?(Proc) options.merge!(base.class.previewable.url_params.call(base)) end if target_project options.merge!(:project_name => target_project.short_name) end options end def <=>(other) self.date <=> other.date end end class ClassBase attr_reader :date_column, :base, :url, :url_params def initialize(base) option = base.previewable_options[:project_scope] @project_scope = if option.is_a?(Proc) option elsif base.column_names.include?('project_id') Proc.new do |projects| projects = projects.blank? ? [Project.current] : projects.flatten.uniq { :conditions => ["#{base.table_name}.project_id IN (?)", projects.map(&:id)] } end else raise 'Previewable project scope could not be determined' end option = base.previewable_options[:date_column] @date_column = if option && base.column_names.include?(option.to_s) option elsif base.column_names.include?('updated_at') 'updated_at' elsif base.column_names.include?('created_at') 'created_at' else raise 'Previewable date could not be determined' end option = base.previewable_options[:url] params = option.delete(:params) @url = option if params.is_a?(Proc) @url_params = params end if @url[:controller].blank? raise "Previewable url [#{@url.inspect}] is invalid" end @url[:action] ||= 'index' @base = base end def searchable? base.respond_to?(:token_search) end def feedable? !self.feed.blank? end def feed base.previewable_options[:feed] end def find_options base.previewable_options[:find_options].dup rescue nil end def searchable_columns base.previewable_options[:searchable_columns] end def project_scope(projects = Project.current) scope = @project_scope.call([projects].flatten) scope.delete(:order) scope end def viewable?(user = User.current, projects = Project.current) match = [projects].flatten.detect do |project| target_url = project ? url.merge(:project_name => project.short_name) : url user.access_permitted?(target_url) end !match.blank? end end end end ActiveRecord::Base.send(:include, Acts::Previewable) model_paths = Dependencies.load_paths.collect do |pt| pt.match(/models/) ? File.expand_path(pt + '/**/*.rb') : nil end.compact.each do |mp| Dir.glob(mp).reject do |f| !File.read(f).match(/define_previewable/) end.each do |f| File.basename(f, '.rb').camelize.constantize end end