# encoding: utf-8 module ConstructorPages # Page model. Pages are core for company websites, blogs etc. class Page < ActiveRecord::Base include ActiveSupport::Inflector # Adding has_many for all field types Field::TYPES.each do |t| class_eval %{has_many :#{t}_types, dependent: :destroy, class_name: 'Types::#{t.titleize}Type'} end has_many :fields, through: :template belongs_to :template default_scope -> { order :lft } validate :templates_existing_check before_save :friendly_url, :assign_template, :full_url_update after_update :descendants_update after_create :create_fields_values acts_as_nested_set class << self # Used for find page by request. It return first page if no request given or request is home page # @param request for example '/conditioners/split-systems/zanussi' def find_by_request_or_first(request = nil) (request.nil? || request == '/') ? Page.first : Page.find_by(full_url: request) end # Generate full_url from parent id and url # @param parent_id integer # @param url should looks like 'hello-world-page' without leading slash def full_url_generate(parent_id, url = '') '/' + Page.find(parent_id).self_and_ancestors.map{|p| p.url if p.in_url}.append(url).compact.join('/') end # Check code_name for field and template. # When missing method Page find field or page in branch with plural and singular code_name so # field and template code_name should be uniqueness for page methods def check_code_name(code_name) [code_name, code_name.pluralize, code_name.singularize].each {|name| return false if Page.instance_methods.include?(name.to_sym)} true end # Search pages # # Example: # Page.in('/conditioners').search_by(area: 25) # Page.by('price<' => 5000).search_in('/zanussi') # Page.in('/midea').by('price>' => 10000).search(:models) def search(what = nil) _hash, _array = {}, [] @where = Page.find_by(full_url: @where) if @where.is_a?(String) _array = ['lft > ? and rgt < ?', @where.lft, @where.rgt] if @where if what what = what.to_s.singularize.downcase _hash[:template_id] = ConstructorPages::Template.find_by(code_name: what).try(:id) end _hash[:id] = ids_by_params(@params) if @params @where = @params = nil _hash.empty? && _array.empty? ? [] : Page.where(_hash).where(_array).to_a end def in(where = nil); tap {@where = where} end def by(params = nil); tap {@params = params} end def search_in(where = nil); self.in(where).search end def search_by(params = nil); self.by(params).search end # Return ids of pages founded by params def ids_by_params(params) _hash = {} params.each_pair do |key, value| next if key == 'utf8' key = key.to_s _key, _value = key.gsub(/>||$/ || value =~ /^>/ sign = '>' elsif key =~ /<$/ || value =~ /^{price: 500, content: 'Hello'} # @param reset_booleans reset all boolean fields to false before assign params def update_fields_values(params, reset_booleans = true) params || return fields.each {|f| f.find_or_create_type_object(self).tap {|t| t || next t.value = 0 if f.type_value == 'boolean' && reset_booleans params[f.code_name.to_sym].tap {|v| v && t.value = v} t.save }} end # Create fields values def create_fields_values; fields.each {|f| f.create_type_object self} end # Remove all fields values def remove_fields_values; fields.each {|f| f.remove_type_object self} end # Search page by template code_name in same branch of pages and templates. # It allows to call page.category.brand.series.model etc. # # Return one page if founded in ancestors, # and return array of pages if founded in descendants # # It determines if code_name is singular or nor # @param cname template code name def find_page_in_branch(cname) Template.find_by(code_name: cname.singularize).tap {|t| t || return (descendants.where(template_id: t.id) if cname == cname.pluralize).tap {|r| r ||= [] return r.empty? ? ancestors.find_by(template_id: t.id) : r}} end alias_method :find_pages_in_branch, :find_page_in_branch def in_breadcrumbs; in_nav end def published?; active? end # Return true if there is a file upload field in page def multipart? fields.each {|f| return true if f.type_value == 'image'} false end # Returns page hash attributes with fields. # # Default attributes are name and title. Options param allows to add more. # @param options default merge name and title page attributes def as_json(options = {}) {name: self.name, title: self.title}.merge(options).tap do |options| fields.each {|f| options.merge!({f.code_name.to_sym => f.get_value_for(self)})} end end # Check if link specified def redirect?; url != redirect && !redirect.empty? end # Touch all pages in same branch def touch_branch [ancestors, descendants].each {|p| p.map(&:touch)} end # When method missing it get/set field value or get page in branch # # Examples: # page.content = 'Hello world' # puts page.price # page.brand.models.each do... def method_missing(name, *args, &block) super && return if new_record? name = name.to_s name[-1] == '=' ? set_field_value(name[0..-2], args[0]) : get_field_value(name) || find_pages_in_branch(name) end private # if url has been changed by manually or url is empty def friendly_url self.url = ((auto_url || url.empty?) ? parameterize(transliterate(name, '')) : url) end # Page is not valid if there is no template def templates_existing_check; errors.add_on_empty(:template_id) if Template.count == 0 end # If template_id is nil then get first template def assign_template; self.template_id = Template.first.id unless template_id end # Update full_url def full_url_update self.full_url = (parent_id ? Page.full_url_generate(parent_id, url) : '/' + url) end # Reload all descendants def descendants_update descendants.map(&:save) if self.full_url_changed? or self.in_url_changed? end end end