PageRenderTime 50ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/app/models/page.rb

https://github.com/ryanfortin/qmpass
Ruby | 316 lines | 264 code | 49 blank | 3 comment | 33 complexity | d955f820c556bded0f5b5d5e31ba4c88 MD5 | raw file
Possible License(s): MIT, GPL-2.0, BSD-3-Clause
  1. class Page < ActiveRecord::Base
  2. class MissingRootPageError < StandardError
  3. def initialize(message = 'Database missing root page'); super end
  4. end
  5. # Callbacks
  6. before_save :update_published_at, :update_virtual
  7. after_save :save_page_parts
  8. # Associations
  9. acts_as_tree :order => 'virtual DESC, title ASC'
  10. has_many :parts, :class_name => 'PagePart', :order => 'id', :dependent => :destroy
  11. belongs_to :layout
  12. belongs_to :created_by, :class_name => 'User'
  13. belongs_to :updated_by, :class_name => 'User'
  14. # Validations
  15. validates_presence_of :title, :slug, :breadcrumb, :status_id, :message => 'required'
  16. validates_length_of :title, :maximum => 255, :message => '{{count}}-character limit'
  17. validates_length_of :slug, :maximum => 100, :message => '{{count}}-character limit'
  18. validates_length_of :breadcrumb, :maximum => 160, :message => '{{count}}-character limit'
  19. validates_format_of :slug, :with => %r{^([-_.A-Za-z0-9]*|/)$}, :message => 'invalid format'
  20. validates_uniqueness_of :slug, :scope => :parent_id, :message => 'slug already in use for child of parent'
  21. validates_numericality_of :id, :status_id, :parent_id, :allow_nil => true, :only_integer => true, :message => 'must be a number'
  22. validate :valid_class_name
  23. include Radiant::Taggable
  24. include StandardTags
  25. include Annotatable
  26. annotate :description
  27. attr_accessor :request, :response
  28. set_inheritance_column :class_name
  29. def layout_with_inheritance
  30. unless layout_without_inheritance
  31. parent.layout if parent?
  32. else
  33. layout_without_inheritance
  34. end
  35. end
  36. alias_method_chain :layout, :inheritance
  37. def parts_with_pending(reload = false)
  38. @page_parts = nil if reload
  39. @page_parts || parts_without_pending(reload)
  40. end
  41. alias_method_chain :parts, :pending
  42. def parts_with_pending=(collection)
  43. if collection.all? {|item| item.is_a? PagePart }
  44. self.parts_without_pending = collection
  45. else
  46. self.updated_at_will_change!
  47. @page_parts = collection.map { |item| PagePart.new(item) }
  48. end
  49. end
  50. alias_method_chain :parts=, :pending
  51. def description
  52. self["description"]
  53. end
  54. def description=(value)
  55. self["description"] = value
  56. end
  57. def cache?
  58. true
  59. end
  60. def child_url(child)
  61. clean_url(url + '/' + child.slug)
  62. end
  63. def headers
  64. { 'Status' => ActionController::Base::DEFAULT_RENDER_STATUS_CODE }
  65. end
  66. def part(name)
  67. if new_record? or parts.to_a.any?(&:new_record?)
  68. parts.to_a.find {|p| p.name == name.to_s }
  69. else
  70. parts.find_by_name name.to_s
  71. end
  72. end
  73. def has_part?(name)
  74. !part(name).nil?
  75. end
  76. def has_or_inherits_part?(name)
  77. has_part?(name) || inherits_part?(name)
  78. end
  79. def inherits_part?(name)
  80. !has_part?(name) && self.ancestors.any? { |page| page.has_part?(name) }
  81. end
  82. def published?
  83. status == Status[:published]
  84. end
  85. def status
  86. Status.find(self.status_id)
  87. end
  88. def status=(value)
  89. self.status_id = value.id
  90. end
  91. def url
  92. if parent?
  93. parent.child_url(self)
  94. else
  95. clean_url(slug)
  96. end
  97. end
  98. def process(request, response)
  99. @request, @response = request, response
  100. if layout
  101. content_type = layout.content_type.to_s.strip
  102. @response.headers['Content-Type'] = content_type unless content_type.empty?
  103. end
  104. headers.each { |k,v| @response.headers[k] = v }
  105. @response.body = render
  106. @request, @response = nil, nil
  107. end
  108. def render
  109. if layout
  110. parse_object(layout)
  111. else
  112. render_part(:body)
  113. end
  114. end
  115. def render_part(part_name)
  116. part = part(part_name)
  117. if part
  118. parse_object(part)
  119. else
  120. ''
  121. end
  122. end
  123. def render_snippet(snippet)
  124. parse_object(snippet)
  125. end
  126. def find_by_url(url, live = true, clean = true)
  127. return nil if virtual?
  128. url = clean_url(url) if clean
  129. my_url = self.url
  130. if (my_url == url) && (not live or published?)
  131. self
  132. elsif (url =~ /^#{Regexp.quote(my_url)}([^\/]*)/)
  133. slug_child = children.find_by_slug($1)
  134. if slug_child
  135. found = slug_child.find_by_url(url, live, clean)
  136. return found if found
  137. end
  138. children.each do |child|
  139. found = child.find_by_url(url, live, clean)
  140. return found if found
  141. end
  142. file_not_found_types = ([FileNotFoundPage] + FileNotFoundPage.descendants)
  143. file_not_found_names = file_not_found_types.collect { |x| x.name }
  144. condition = (['class_name = ?'] * file_not_found_names.length).join(' or ')
  145. condition = "status_id = #{Status[:published].id} and (#{condition})" if live
  146. children.find(:first, :conditions => [condition] + file_not_found_names)
  147. end
  148. end
  149. def to_xml(options={}, &block)
  150. super(options.reverse_merge(:include => :parts), &block)
  151. end
  152. class << self
  153. def find_by_url(url, live = true)
  154. root = find_by_parent_id(nil)
  155. raise MissingRootPageError unless root
  156. root.find_by_url(url, live)
  157. end
  158. def display_name(string = nil)
  159. if string
  160. @display_name = string
  161. else
  162. @display_name ||= begin
  163. n = name.to_s
  164. n.sub!(/^(.+?)Page$/, '\1')
  165. n.gsub!(/([A-Z])/, ' \1')
  166. n.strip
  167. end
  168. end
  169. @display_name = @display_name + " - not installed" if missing? && @display_name !~ /not installed/
  170. @display_name
  171. end
  172. def display_name=(string)
  173. display_name(string)
  174. end
  175. def load_subclasses
  176. ([RADIANT_ROOT] + Radiant::Extension.descendants.map(&:root)).each do |path|
  177. Dir["#{path}/app/models/*_page.rb"].each do |page|
  178. $1.camelize.constantize if page =~ %r{/([^/]+)\.rb}
  179. end
  180. end
  181. if ActiveRecord::Base.connection.tables.include?('pages') && Page.column_names.include?('class_name') # Assume that we have bootstrapped
  182. Page.connection.select_values("SELECT DISTINCT class_name FROM pages WHERE class_name <> '' AND class_name IS NOT NULL").each do |p|
  183. begin
  184. p.constantize
  185. rescue NameError, LoadError
  186. eval(%Q{class #{p} < Page; def self.missing?; true end end}, TOPLEVEL_BINDING)
  187. end
  188. end
  189. end
  190. end
  191. def new_with_defaults(config = Radiant::Config)
  192. default_parts = config['defaults.page.parts'].to_s.strip.split(/\s*,\s*/)
  193. page = new
  194. default_parts.each do |name|
  195. page.parts << PagePart.new(:name => name, :filter_id => config['defaults.page.filter'])
  196. end
  197. default_status = config['defaults.page.status']
  198. page.status = Status[default_status] if default_status
  199. page
  200. end
  201. def is_descendant_class_name?(class_name)
  202. (Page.descendants.map(&:to_s) + [nil, "", "Page"]).include?(class_name)
  203. end
  204. def descendant_class(class_name)
  205. raise ArgumentError.new("argument must be a valid descendant of Page") unless is_descendant_class_name?(class_name)
  206. if ["", nil, "Page"].include?(class_name)
  207. Page
  208. else
  209. class_name.constantize
  210. end
  211. end
  212. def missing?
  213. false
  214. end
  215. end
  216. private
  217. def valid_class_name
  218. unless Page.is_descendant_class_name?(class_name)
  219. errors.add :class_name, "must be set to a valid descendant of Page"
  220. end
  221. end
  222. def attributes_protected_by_default
  223. super - [self.class.inheritance_column]
  224. end
  225. def update_published_at
  226. self[:published_at] = Time.now if published? and !published_at
  227. true
  228. end
  229. def update_virtual
  230. unless self.class == Page.descendant_class(class_name)
  231. self.virtual = Page.descendant_class(class_name).new.virtual?
  232. else
  233. self.virtual = virtual?
  234. end
  235. true
  236. end
  237. def clean_url(url)
  238. "/#{ url.strip }/".gsub(%r{//+}, '/')
  239. end
  240. def parent?
  241. !parent.nil?
  242. end
  243. def lazy_initialize_parser_and_context
  244. unless @parser and @context
  245. @context = PageContext.new(self)
  246. @parser = Radius::Parser.new(@context, :tag_prefix => 'r')
  247. end
  248. @parser
  249. end
  250. def parse(text)
  251. lazy_initialize_parser_and_context.parse(text)
  252. end
  253. def parse_object(object)
  254. text = object.content
  255. text = parse(text)
  256. text = object.filter.filter(text) if object.respond_to? :filter_id
  257. text
  258. end
  259. def save_page_parts
  260. if @page_parts
  261. self.parts_without_pending.clear
  262. @page_parts.each {|p| self.parts_without_pending << p }
  263. end
  264. @page_parts = nil
  265. true
  266. end
  267. end