Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Extracted as a raw gem from the parent project

  • Loading branch information...
commit 291f634f5b5a3d40bbe7b3aa66fad383edc4e514 1 parent 626e7bb
@NoICE authored
View
3  .gitignore
@@ -1,4 +1,5 @@
+*.swp
*.gem
.bundle
Gemfile.lock
-pkg/*
+pkg/*
View
3  CHANGELOG.md
@@ -0,0 +1,3 @@
+# 0.0.1
+
+- first version as a gem
View
20 LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2010-2011 by NoICE
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
View
171 README.md
@@ -0,0 +1,171 @@
+# HexCMS for Rails
+
+You are looking at one powerful CMS system for Rails 3+ that is built with
+flexibility and freedom to do anything in mind.
+
+This gem was built from the ground up to create CMS system that doesn't tie
+you, the developer. It is so simple to overwrite any part of it and
+it's YOU who chose which action is taken where... and which way.
+
+# Features and How It Works
+
+## Basic
+
+With this gem, you can create sections of your website that are completely
+manageable from the database. It supports localization, custom layouts,
+custom templates, data-driven boxes placeable everywhere (which uses their own configurable templates)...
+See [hexcms-demo](https://github.com/noice/hexcms-demo) for example of the
+complete application built with this gem.
+
+Basically it goes like this.
+You have a *Page* (`Cms::Page`) object, which have it's name, content, path (url *slug*), code, locale id, html meta tags...
+Then you have *Layout* (`Cms::Layout`) object, which is assigned to the *Page*.
+If you use `CmsController` which this gem provides (details in **Basic Configuration** section),
+then this *Layout* is used to determine which template will render this page content.
+If you define the default route to this controller, cms controller will try to find `Cms::Page` with that URL and will handle it.
+
+And if static pages is the only thing you want, then that's it.
+
+## Boxes
+
+Then you can add some more sugar to it by using *Boxes*. One *Page* can have many *Boxes* (m:n).
+*Box* have it's own template. *Boxes* can be sorted on the *Page* (by setting it's `priority`).
+Boxes are rendered by *Page*'s template (by manual call to heper `render_boxes(@cms_page.boxes.all)`).
+Boxes have `cms_section` `Hex::Enum` attribute, which you can use to determine position of the box in the template
+(`render_boxes(@cms_page.boxes_with_section(CmsSection::BOX_SECTION_1))`, see the code for section codes).
+
+With this configuration, you can create *Page* which will behave as an article (assigning a proper template to it).
+
+Take look at this article example.
+I want an article, which will have 2 sections. One for article content and one for photos.
+I want adwords panel between the sections.
+So I will create a template called `cms/article.html.haml`.
+I will put just this code inside it:
+
+ %h1= @cms_page.name
+ =render_boxes(@cms_page.boxes_with_section(CmsSection::BOX_SECTION_1))
+ -# code for google adwords...
+ =render_boxes(@cms_page.boxes_with_section(CmsSection::BOX_SECTION_2))
+
+Then I will create `Cms::Layout` pointing to that template.
+
+Then I will create `Cms::Page` with that newly created `layout`.
+
+Then I will create another template in `cms/_photo.html.erb` (and `Cms::Layout` pointing to it) with this content:
+
+ This is some nicely formatted Box.
+ =@cms_box.name
+ =raw @cms_box.desc
+
+Then I will create some `Cms::Box`es with this template and assign them to that page via `Cms::PageBox` (.boxes) relationship,
+of course with .cms_section set to that `CmsSection::BOX_SECTION_1/2`.
+And that's it, the page will render the main page content and all boxes on it.
+It's up to you what you will put into those templates...
+
+## Menus
+
+You can use `Cms::Menu` to build your main menu, footer columns, copyright notice (i.e. links to pages, like TOS, etc.).
+Every menu object points to some target. Target is polymorphic, for future uses.
+
+If the target is `Cms::Page`, menu's name, desc, slug attributes are synced with it.
+
+To see where that menu item leads I developed a model called `Cms::MenuRoute`.
+This model have column `code` which is parsed by `cms_menu_path` (and `*_url`).
+The code is composed of the rails route name (e.g. `root_path`, `article_path`, but mainly `cms_page_path`, but without the `_path` suffix)
+and (what I call) *an ID provider*, which is basically method which will provide ID parameter for that route.
+
+That means this: if the code is *cms_page:slug*, then the method `.slug` will be called on the menus `.target`...
+and result of that will be passed to `cms_page_path`. So the final call is `cms_page_path(target.slug)`
+(which should exist as Rails helper generates by the routes).
+
+The whole point of this is that you can have link to your non-cms *Login* page defined in the menu for example...
+Or you can have your `ArticleCategory` controller which can show articles for category with that name... About Us... etc.
+
+In my usecases I just needed those tho `Cms::MenuRoute`s:
+
+ Cms::MenuRoute.create(:name => "Homepage", :code => "root")
+ Cms::MenuRoute.create(:name => "CMS page", :code => "cms_page:slug")
+
+Then you can render the menu like this:
+
+ <% Cms::Menu.with_section(CmsSection::MENU_TOP).each do |m| %>
+ <%= link_to m.name, cms_menu_path(m) %>
+ <% end %>
+
+# Installation
+
+## Dependencies
+
+- Rails 3.x
+- [nested_set](https://github.com/skyeagle/nested_set) gem (which is used for the menu)
+- [hexcore-rails](https://github.com/noice/hexcore-rails) gem (which provides *slug* functionality for example).
+
+There are only a few steps to get this CMS up and running (and serving your pages).
+
+## Basic configuration
+
+- Add `gem 'hexcms-rails'` to your Gemfile, run `bundle`
+- Add `include Hex::Cms::ControllerMethods` to your application controller.
+ This will install the dynamic path helpers (i.e. `cms_menu_path` and `cms_menu_url`)
+ that you can use in your views and other controllers.
+ It is not injected automatically on purpose, so you have the control.
+- Add this route to your routes.rb:
+ `match ':slug' => 'cms#show', :as => :cms_page`
+ (You can customize this if you wish.)
+
+## Creating the necessary models and controllers
+
+### Models
+
+- Create model `Cms::Layout` which include `Hex::Cms::LayoutBase`
+- Create model `Cms::Menu` which include `Hex::Cms::MenuBase`
+- Create model `Cms::Page` which include `Hex::Cms::PageBase`
+- Create model `Cms::Box` which include `Hex::Cms::BoxBase`
+(you can copy these models from the provided example (link above), next version of this gem will have generators for them)
+
+### Controllers
+
+There is only one controller needed... and thats the controller which will
+process requests for static pages.
+Create the controller with name `CmsController` which must descend from
+
+## Creating layouts
+
+Create some page-layout file in `app/views/cms` (e.g. *home.html.erb*;
+note that you can place cms templates anywhere, `app/views/cms` is just my convention).
+
+Add this layout to `cms_layouts` table. E.g.
+
+ Cms::Layout.create(:name => "Homepage", :file => "application", :template => "cms/home")
+
+- `:name` is just the layout name, can be used for your admin page.
+- `:file` is the layout file (i.e. rails layout, e.g. `app/layouts/application.html.erb`)
+- `:template` is the template file, e.g. `app/views/cms/home.html.erb`
+
+Since this moment, you can use this template for your `Cms::Page`s.
+For `Cms::Box`es, the template must be a partial (i.e. with underscore).
+The name for `Cms::Layout` will remain unchanged though (i.e. without underscore).
+Only you (via your own structure) control which way you handle it.
+
+# Customization
+
+You can override nearly every part of the functionality, or you can use only
+what you need.
+Check the source code (mainly the *Base modules) to see what you can override.
+
+# Testing
+
+There are some tests, but for now, they are tied to the parent application
+from which this gem is extracted.
+So no proper standalone tests are included for now.
+This will change in the future.
+
+# Contribution
+
+If you want to contribute to this project, please send me a pull request
+and I'll look on it.
+But please bear in mind that this gem should remain as abstract and lightweight as possible.
+
+# License
+
+Copyright &copy; 2011 NoICE, released under the MIT license
View
2  TODO.md
@@ -0,0 +1,2 @@
+- generator for models and migrations
+ see [activeadmin](https://github.com/gregbell/active_admin/blob/master/lib/generators/active_admin/install/install_generator.rb)
View
27 app/helpers/hex/cms/box_helper.rb
@@ -0,0 +1,27 @@
+# encoding: utf-8
+module Hex
+ module Cms
+ module BoxHelper
+
+ # renders given Cms::Box objects
+ # with their partials
+ # boxes can be separated by some separator, like separating div, if that's needed
+ def render_boxes(boxes, separator = nil)
+ boxes = boxes.all if boxes.respond_to?(:all)
+ raise ArgumentError, "expected collection of Cms::Box instances" unless boxes.kind_of?(Array)
+ out = ''
+ boxes.each do |box|
+ out << render_box(box)
+
+ out << separator if separator && box != boxes.last
+ end
+ out.html_safe
+ end
+
+ def render_box(box)
+ box && render(box.layout_template, :box => box)
+ end
+
+ end
+ end
+end
View
15 app/helpers/hex/cms/link_helper.rb
@@ -0,0 +1,15 @@
+# encoding: utf-8
+module Hex
+ module Cms
+ module LinkHelper
+
+ # generates URL for given menu
+ def cms_menu_path_for_code(code, opts = {})
+ menu = ::Cms::Menu.where(:code => code).with_locale(fetch_locale).first
+ return nil unless menu
+ cms_menu_path(menu, opts)
+ end
+
+ end
+ end
+end
View
28 app/models/cms_section.rb
@@ -0,0 +1,28 @@
+# encoding: utf-8
+class CmsSection < Hex::Enum
+ # basic boxes
+ BOX = new(100, 'box')
+ BOX_TOP = new(200, 'box_top')
+ BOX_BOTTOM = new(300, 'box_bottom')
+ BOX_SECTION_1 = new(1000, 'box_section_1')
+ BOX_SECTION_2 = new(2000, 'box_section_2')
+ BOX_SECTION_3 = new(3000, 'box_section_3')
+ BOX_SECTION_4 = new(4000, 'box_section_4')
+ BOX_SECTION_5 = new(5000, 'box_section_5')
+
+ # menu sections
+ MENU = new(10000, 'menu')
+ MENU_FOOTER_1 = new(20000, 'menu_footer_1')
+ MENU_FOOTER_2 = new(30000, 'menu_footer_2')
+ MENU_COPYRIGHT = new(40000, 'menu_copyright')
+ MENU_TOP = new(50000, 'menu_top')
+ MENU_SUP = new(60000, 'menu_sup')
+
+ alias_method :code, :label
+
+ class << self
+ alias_method :find_by_code, :find_by_label
+ alias_method :all, :items
+ end
+
+end
View
74 app/models/hex/cms/locale.rb
@@ -0,0 +1,74 @@
+# encoding: utf-8
+
+#
+# Simple locale enum class
+#
+module Hex
+ module Cms
+ class Locale
+
+ EN = 1
+ CZ = 2
+
+ def initialize(locale_id)
+ raise ArgumentError, "expected valid locale_id" unless self.class.locales.keys.include?(locale_id)
+ @locale_id = locale_id
+ end
+
+ def id
+ @locale_id
+ end
+
+ def name
+ self.class.name_for(@locale_id)
+ end
+
+ def code
+ self.class.code_for(@locale_id)
+ end
+
+ class << self
+
+ def locales
+ {
+ CZ => 'CZ',
+ EN => 'EN'
+ }
+ end
+
+ def find_by_code(code)
+ code = code.to_s.upcase
+ locale_id = locales.collect{|k, v| v == code ? k : nil}.compact.first
+ self.new(locale_id)
+ end
+
+ # TODO move this shit away
+ def code_for(locale_id)
+ raise ArgumentError, "expected valid locale_id, #{locale_id.inspect} given" if !locales.key?(locale_id)
+ locales[locale_id]
+ end
+
+ # nice feature - grabs model name by it's code from I18n
+ # locale files
+ # e.g. :status => { :new => "New" }
+ # Defaults to attribute value stored in database if not found by I18n
+ def name_for(locale_id)
+ code = code_for(locale_id)
+ I18n.t("locale.#{code.downcase}", code)
+ end
+
+ def find(locale_id)
+ locale_id = locale_id.try(:to_i)
+ return nil unless locales.keys.include?(locale_id)
+ new(locale_id)
+ end
+
+ def all
+ self.locales.keys.collect { |locale_id| Locale.new(locale_id) }
+ end
+
+ end
+
+ end
+ end
+end
View
75 app/models/hex/cms/status.rb
@@ -0,0 +1,75 @@
+# encoding: utf-8
+module Hex
+ module Cms
+ class Status
+
+ INACTIVE = 1
+ ACTIVE = 2
+ REMOVED = 3
+
+ def initialize(status_id)
+ raise ArgumentError, "expected valid status_id" unless self.class.statuses.keys.include?(status_id)
+ @status_id = status_id
+ end
+
+ def id
+ @status_id
+ end
+
+ def name
+ self.class.name_for(@status_id)
+ end
+
+ def code
+ self.class.code_for(@status_id)
+ end
+
+ class << self
+
+ def statuses
+ {
+ INACTIVE => 'INACTIVE',
+ ACTIVE => 'ACTIVE',
+ REMOVED => 'REMOVED',
+ }
+ end
+
+ # TODO search & destroy
+ def code_for(status_id)
+ raise ArgumentError, "expected valid status_id, #{status.inspect} given" if !statuses.key?(status_id)
+ statuses[status_id]
+ end
+
+ # grabs model name by it's code from I18n
+ # locale files
+ # e.g. :status => { :new => "New" }
+ # Defaults to attribute value stored in database if not found by I18n
+ def name_for(status_id)
+ code = code_for(status_id)
+ I18n.t("status.#{code.downcase}", code)
+ end
+
+ def find(status_id)
+ status_id = status_id.try(:to_i)
+ return nil unless statuses.keys.include?(status_id)
+ new(status_id)
+ end
+
+ def all
+ self.statuses.keys.collect { |status_id| Status.new(status_id) }
+ end
+
+ # scope for activity-based statuses
+ def activity
+ [ACTIVE, INACTIVE].collect { |status_id| Status.new(status_id) }
+ end
+
+ def activity_with_removal
+ [ACTIVE, INACTIVE, REMOVED].collect { |status_id| Status.new(status_id) }
+ end
+
+ end
+
+ end
+ end
+end
View
5 config/hex/cms/cz.yml
@@ -0,0 +1,5 @@
+cz:
+ hex:
+ cms:
+ error404:
+ title: "404 - Strana nenalezena"
View
3  config/hex/cms/en.yml
@@ -0,0 +1,3 @@
+en:
+ hex:
+ cms:
View
18 hexcms-rails.gemspec
@@ -1,24 +1,24 @@
# -*- encoding: utf-8 -*-
$:.push File.expand_path("../lib", __FILE__)
-require "hexcms-rails/version"
+require "hex/cms/version"
Gem::Specification.new do |s|
s.name = "hexcms-rails"
- s.version = Hexcms::Rails::VERSION
+ s.version = Hex::Cms::VERSION
s.authors = ["noice"]
s.email = ["noice@email.cz"]
- s.homepage = ""
- s.summary = %q{TODO: Write a gem summary}
- s.description = %q{TODO: Write a gem description}
+ s.homepage = "http://noice.cz"
+ s.summary = %q{Hexmotive CMS for Rails 3 applications}
+ s.description = %q{Powerful CMS system built with freedom in mind}
- s.rubyforge_project = "hexcms-rails"
+ #s.rubyforge_project = "hexcms-rails"
s.files = `git ls-files`.split("\n")
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
s.require_paths = ["lib"]
- # specify any dependencies here; for example:
- # s.add_development_dependency "rspec"
- # s.add_runtime_dependency "rest-client"
+ s.add_runtime_dependency(%q<rails>, [">= 3.0.0"])
+ s.add_runtime_dependency(%q<nested_set>, ["~> 1.6.5"])
+ #s.add_runtime_dependency(%q<hexcore-rails>, ["~> 0.1.1"])
end
View
42 lib/hex/cms/box_base.rb
@@ -0,0 +1,42 @@
+# encoding: utf-8
+module Hex
+ module Cms
+ module BoxBase
+
+ extend ActiveSupport::Concern
+
+ def self.included(base)
+ base.send :include, Hex::Cms::LocaleModule
+ base.send :include, Hex::Cms::CodeModule
+ base.has_enum :cms_section
+ # associations
+ base.belongs_to :layout, :class_name => "Cms::Layout"
+ base.has_many :page_boxes, :class_name => "Cms::PageBox", :foreign_key => :box_id, :dependent => :destroy
+ base.has_many :pages, :through => :page_boxes
+ # validations
+ base.validates :name, :presence => true, :uniqueness => true
+ base.validates :layout, :presence => true
+ # callbacks
+ base.before_validation :set_defaults
+ # delegates
+ base.delegate :name, :template, :to => :layout, :prefix => true, :allow_nil => true
+ base.delegate :name, :code, :to => :cms_section, :prefix => true, :allow_nil => true
+ # scopes
+ base.scope :ordered, base.order("#{base.table_name}.`priority` DESC")
+ end
+
+ module InstanceMethods
+
+ def set_defaults
+ self.cms_section = CmsSection::BOX if self.cms_section.blank?
+ end
+
+ def name_with_type
+ [self.cms_section_name, self.name].join(' - ')
+ end
+
+ end
+
+ end
+ end
+end
View
110 lib/hex/cms/cms_controller_base.rb
@@ -0,0 +1,110 @@
+# encoding: utf-8
+module Hex
+ module Cms
+
+ # Base controller which provides default functionality
+ # for Cms::Page routed to it.
+ # It does all the work by default, so subclassed controller
+ # should only add callbacks it needs or it can override the default
+ # behavior.
+ # Feel free to override any of those to fit your needs.
+ module CmsControllerBase
+
+ extend ActiveSupport::Concern
+
+ def self.included(base)
+ base.before_filter :load_cms_page, :load_cms_layout, :load_cms_menu, :only => [:show]
+
+ base.layout :fetch_layout
+
+ base.helper_method :fetch_keywords
+ base.helper_method :fetch_web_description
+ end
+
+ module InstanceMethods
+
+ protected
+
+ # Returns string which contains name of the layout to be rendered.
+ # Is used by +layout+ Rails helper.
+ # By default, it uses @cms_layout.file.
+ def fetch_layout
+ @cms_layout.file.nil? ? 'application' : @cms_layout.file
+ end
+
+ # Should return content for meta keywords tag.
+ # Returns @cms_page.meta_kw by default.
+ def fetch_web_keywords
+ return '' if @cms_page.blank?
+ @cms_page.meta_kw
+ end
+
+ # Should return content for meta description tag.
+ # Returns @cms_page.meta_kw by default.
+ def fetch_web_description
+ return '' if @cms_page.blank?
+ @cms_page.meta_desc
+ end
+
+ # Loads the page slug from params.
+ # Can do any overrides for special cases as well or compose it
+ # based on any conditions may occur.
+ def fetch_slug
+ # lets find this page
+ params[:slug]
+ end
+
+ # Loads Cms::Page model with slug loaded from +fetch_slug+
+ # and stores it into @cms_page object.
+ # Calls +render_404+ if that Cms::Page is not found.
+ def load_cms_page
+ slug = fetch_slug
+ @cms_page = ::Cms::Page.with_slug(slug).with_locale(fetch_locale).first
+
+ if @cms_page.blank?
+ logger.error "#{self.class}: Cms::Page with slug '#{slug}' doesn't exist!"
+ render_404
+ return
+ end
+ end
+
+ # Loads Cms::Layout instance which will be used to determine
+ # in which layout the currently displayed Cms::Page will be rendered.
+ # Uses layout defined in @cms_page instance variable by default.
+ def load_cms_layout
+ @cms_layout = @cms_page.layout
+ end
+
+ # Loads Cms::Menu for current page.
+ # Page isn't forced to have menu. In that case,
+ # @cms_menu instance variable is +nil+ and the CMS doesn't mind.
+ # Uses menu associated with the current page by default.
+ def load_cms_menu
+ @cms_menu = @cms_page.menu
+ end
+
+ public
+
+ # Special page for 404 error, can be used in the routes
+ def show_404
+ render_404
+ end
+
+ # This is the frontend for the CMS.
+ # It's the default CMS action which should eat all CMS-related related routes.
+ # It's task is to proceed CMS layouts and page, load everything that is needed
+ # for the layout and render all of it properly.
+ def show
+ @web_title = @cms_page.meta_title.blank? ? @cms_page.name : @cms_page.meta_title
+ @h1_title = @cms_page.h1_title.present? ? @cms_page.h1_title : @web_title
+
+ # render show action with given layout
+ render :template => (@cms_layout.template || "cms/show"), :layout => @cms_layout.file
+ end
+
+ end
+
+ end
+
+ end
+end
View
18 lib/hex/cms/code_module.rb
@@ -0,0 +1,18 @@
+# encoding: utf-8
+module Hex
+ module Cms
+ module CodeModule
+
+ extend ActiveSupport::Concern
+
+ def self.included(base)
+ end
+
+ module ClassMethods
+ def with_code(code)
+ where(:code => code)
+ end
+ end
+ end
+ end
+end
View
88 lib/hex/cms/controller_methods.rb
@@ -0,0 +1,88 @@
+# encoding: utf-8
+module Hex
+ module Cms
+ module ControllerMethods
+
+ extend ActiveSupport::Concern
+
+ def self.included(base)
+ return unless base.respond_to?(:helper_method)
+ base.helper_method :current_menu, :current_menu?
+ base.helper_method :cms_menu_path
+ base.helper_method :cms_menu_url
+ end
+
+ module InstanceMethods
+
+ # proxy to the Rails default logger
+ def logger
+ Rails.logger
+ end
+
+ # gets current menu item
+ # from rails request
+ # you can override this in article controller for example
+ def current_menu
+ # if we know @cms_page and it's menu, we are lucky...
+ return @cms_page.menu if @cms_page && @cms_page.menu
+ return @cms_menu if @cms_menu
+
+ # ... if we don't, we must find other way to get it...
+ # that means we must override this method!
+ nil
+ end
+
+ # used when rendering menu... to check if currently rendered item
+ # is above (ancestor of) current menu
+ def current_menu?(rendered_item)
+ return true if cms_menu_path(rendered_item) == request.path
+
+ m = self.current_menu
+ return false if m.blank?
+ rendered_item.is_or_is_ancestor_of?(m) || rendered_item.is_or_is_descendant_of?(m)
+ end
+
+ # returns path for link to given menu item
+ # @param [Cms::Menu] item
+ # TODO move to Cms::Menu, if possible - url generation should be it's own job..
+ # at least it could generate the whole string for eval (because routes aren't recognized within model)
+ def cms_menu_path(item, opts = {})
+ raise ArgumentError, "cms_menu_path: first parameter - Cms::Menu expected, got #{item.inspect}" unless item.kind_of?(::Cms::Menu)
+ tmp, id_provider = item.menu_route_code.split(':')
+ if id_provider.blank?
+ logger.debug "cms_menu_path: calling #{tmp} via send..."
+ send(:"#{tmp}_path", opts)
+ else
+ # let's get value for this id_provider
+ id = item.send(id_provider)
+ logger.debug "cms_menu_path: calling #{tmp} via send... with #{id_provider} as parameter (#{id})"
+ send(:"#{tmp}_path", id, opts)
+ end
+ end
+
+ def cms_menu_url(item, opts = {})
+ raise ArgumentError, "cms_menu_url: first parameter - Cms::Menu expected, got #{item.class.name}" unless item.kind_of?(::Cms::Menu)
+ tmp, id_provider = item.menu_route_code.split(':')
+ if id_provider.blank?
+ logger.debug "cms_menu_url: calling #{tmp} via send..."
+ send(:"#{tmp}_url", opts)
+ else
+ # let's get value for this id_provider
+ id = item.send(id_provider)
+ logger.debug "cms_menu_url: calling #{tmp} via send... with #{id_provider} as parameter (#{id})"
+ send(:"#{tmp}_url", id, opts)
+ end
+ end
+
+ # render 404 page from anywhere
+ def render_404
+ @web_title = t('cms.error404.title')
+ respond_to do |format|
+ format.html { render :template => '/cms/show_404', :layout => 'application', :status => 404 }
+ format.all { render :text => t('cms.error404.title'), :status => 404, :content_type => 'text/html' }
+ end
+ end
+ end
+ end
+ end
+end
View
28 lib/hex/cms/layout_base.rb
@@ -0,0 +1,28 @@
+# encoding: utf-8
+module Hex
+ module Cms
+ module LayoutBase
+
+ extend ActiveSupport::Concern
+
+ def self.included(base)
+ # associations
+ base.has_many :pages, :class_name => "Cms::Page", :foreign_key => "layout_id"
+ # validations
+ base.validates :name, :presence => true, :uniqueness => true
+ base.validates :template, :presence => true
+
+ base.before_validation :set_defaults
+ end
+
+ module InstanceMethods
+
+ def set_defaults
+ self.file = 'application' if self.file.blank?
+ end
+
+ end
+
+ end
+ end
+end
View
53 lib/hex/cms/locale_module.rb
@@ -0,0 +1,53 @@
+# encoding: utf-8
+module Hex
+ module Cms
+ module LocaleModule
+
+ extend ActiveSupport::Concern
+
+ def self.included(base)
+ base.delegate :name, :code, :to => :locale, :prefix => true, :allow_nil => true
+ end
+
+ module ClassMethods
+
+ # scope for finding item with given locale
+ # returns rows that have this specific locale OR are shared across all locales
+ def with_locale_id(locale_id)
+ where("`#{self.table_name}`.`locale_id` = ? OR `#{self.table_name}`.`locale_id` IS NULL", locale_id)
+ end
+
+ # scope for finding item with given locale
+ def with_locale(locale)
+ #raise ArgumentError, "Supplied argument must be Locale object" unless locale.kind_of?(::Hex::Cms::Locale)
+ with_locale_id(locale.id)
+ end
+
+ def locale_cz
+ where(:locale_id => ::Hex::Cms::Locale::CZ)
+ end
+
+ def locale_en
+ where(:locale_id => ::Hex::Cms::Locale::EN)
+ end
+
+ end
+
+ module InstanceMethods
+
+ def locale
+ return nil unless self.locale_id
+ @locale ||= ::Hex::Cms::Locale.new(self.locale_id)
+ end
+
+ def locale_id=(locale_id)
+ self[:locale_id] = locale_id
+ @locale = nil
+ locale_id
+ end
+
+ end
+
+ end
+ end
+end
View
99 lib/hex/cms/menu_base.rb
@@ -0,0 +1,99 @@
+# encoding: utf-8
+module Hex
+ module Cms
+ module MenuBase
+
+ extend ActiveSupport::Concern
+
+ def self.included(base)
+ # addons
+ base.acts_as_nested_set
+ base.send :include, Hex::Cms::LocaleModule
+ base.send :include, Hex::Cms::CodeModule
+ base.send :include, Hex::Cms::StatusModule
+ base.has_enum :cms_section
+
+ # temporary variable for storing next parent after save
+ # (+move_to_child_of(tmp_parent)+ will be performed)
+ base.send :attr_accessor, :tmp_parent
+
+ # associations
+ base.belongs_to :menu_route
+ base.belongs_to :target, :polymorphic => true
+
+ # scopes
+ base.scope :ordered, base.order(:lft)
+ base.scope :deordered, base.order("#{base.table_name}.`lft` DESC")
+ base.scope :with_slug, lambda { |slug| base.where(:slug => slug) }
+
+ # validations
+ base.validates :menu_route, :presence => true
+
+ # callbacks
+ base.before_validation :set_defaults
+ base.before_validation :derive_target_attrs
+ base.before_validation :determine_megaslug
+
+ # nested sets callback, triggered after tree node is moved
+ base.after_move :determine_and_save_megaslug
+
+ base.after_save :place_in_tree
+
+ # delegates
+ base.delegate :name, :slug, :desc, :to => :target, :prefix => true, :allow_nil => true
+ base.delegate :name, :code, :to => :menu_route, :prefix => true
+ base.delegate :name, :code, :to => :cms_section, :prefix => true, :allow_nil => true
+ end
+
+ module ClassMethods
+
+ def with_section(cms_section)
+ self.where(:cms_section => cms_section)
+ end
+
+ end
+
+ module InstanceMethods
+
+ def set_defaults
+ self.status_id = ::Hex::Cms::Status::ACTIVE if self.status_id.blank?
+ self.cms_section = CmsSection::MENU if self.cms_section.blank?
+ end
+
+ def derive_target_attrs
+ if self.target
+ self.name = self.target_name
+ self.slug = self.target_slug
+ end
+ end
+
+ # can be overriden in special cases
+ def place_in_tree
+ self.move_to_child_of(self.tmp_parent) if !self.tmp_parent.blank? && self.tmp_parent.to_i != self.parent_id
+ end
+
+ # megaslug is complicated thing
+ # generated from slug of all ancestors
+ def determine_megaslug
+ # megaslug is regenerated always...
+ # gather all ancestors, except for root, and create my megaslug from them (and myself)
+ tmp = []
+ tmp.concat(self.ancestors.where("#{self.class.table_name}.`parent_id` IS NOT NULL").all.collect(&:slug)) if self.persisted?
+ tmp << self.slug
+ # update only if the record is changed... we end in infinite loop otherwise! (callback -> save -> callback -> save ...)
+ self.megaslug = tmp.compact.join('/')
+ true
+ end
+
+ # generates megaslug and saves it, if required
+ def determine_and_save_megaslug
+ self.determine_megaslug
+ self.update_attribute(:megaslug, self.megaslug) if self.megaslug_changed?
+ true
+ end
+
+ end
+
+ end
+ end
+end
View
18 lib/hex/cms/menu_route_base.rb
@@ -0,0 +1,18 @@
+# encoding: utf-8
+module Hex
+ module Cms
+ module MenuRouteBase
+
+ extend ActiveSupport::Concern
+
+ def self.included(base)
+ # associations
+ base.has_many :menus, :class_name => "Cms::Menu", :foreign_key => :menu_route_id
+ end
+
+ module InstanceMethods
+ end
+
+ end
+ end
+end
View
66 lib/hex/cms/page_base.rb
@@ -0,0 +1,66 @@
+# encoding: utf-8
+module Hex
+ module Cms
+ module PageBase
+
+ extend ActiveSupport::Concern
+
+ def self.included(base)
+ # addons
+ base.acts_as_slug :slug
+ base.send :include, Hex::Cms::LocaleModule
+ base.send :include, Hex::Cms::CodeModule
+ base.send :include, Hex::Cms::StatusModule
+
+ # associations
+ base.belongs_to :layout, :class_name => "Cms::Layout"
+
+ base.has_many :page_boxes, :class_name => "Cms::PageBox", :foreign_key => :page_id, :dependent => :destroy
+ base.has_many :boxes, :through => :page_boxes
+
+ # validations
+ base.validates :name, :presence => true
+
+ # scopes
+ base.scope :ordered, base.order(:name)
+ base.scope :ordered_by_date, base.order("#{base.table_name}.`published_at` DESC, #{base.table_name}.`created_at` DESC")
+
+ # callbacks
+ base.before_validation :set_defaults
+ base.after_save :update_menu
+
+ # delegates
+ base.delegate :name, :file, :template, :to => :layout, :prefix => true, :allow_nil => true
+ end
+
+ module InstanceMethods
+
+ def set_defaults
+ self.status_id = ::Hex::Cms::Status::ACTIVE if self.status_id.blank?
+ end
+
+ def update_menu
+ m = self.menu
+ # no menu is attached? just return
+ return true if m.blank?
+ m.send(:derive_target_attrs)
+ m.save!
+ end
+
+ # returns first menu associated to this page
+ # TODO prioritize the search... i.e. there should be some way
+ # how to tell "this is your main menu"
+ def menu
+ @menu ||= ::Cms::Menu.where(:target_id => self.id, :target_type => self.class.model_name).first
+ end
+
+ # returns all boxes with given CmsSection
+ def boxes_with_section(cms_section)
+ self.boxes.ordered.with_locale_id(self.locale_id).where(:cms_section => cms_section)
+ end
+
+ end
+
+ end
+ end
+end
View
52 lib/hex/cms/status_module.rb
@@ -0,0 +1,52 @@
+# encoding: utf-8
+module Hex
+ module Cms
+ module StatusModule
+
+ extend ActiveSupport::Concern
+
+ def self.included(base)
+ base.scope :active, base.where(:status_id => ::Hex::Cms::Status::ACTIVE)
+ base.scope :inactive, base.where(:status_id => [::Hex::Cms::Status::INACTIVE, ::Hex::Cms::Status::REMOVED])
+ base.scope :removed, base.where(:status_id => [::Hex::Cms::Status::REMOVED])
+ base.scope :not_removed, base.where("#{base.table_name}.`status_id` <> ?", ::Hex::Cms::Status::REMOVED)
+ base.delegate :name, :code, :to => :status, :prefix => true, :allow_nil => true
+ end
+
+ module ClassMethods
+ # status_id filtering scope
+ def with_status(status_id)
+ where(:status_id => status_id)
+ end
+ end
+
+ module InstanceMethods
+
+ # returns Status object
+ def status
+ return nil unless self.status_id
+ @status ||= ::Hex::Cms::Status.new(self.status_id)
+ end
+
+ def status_id=(status_id)
+ self[:status_id] = status_id
+ @status = nil
+ status_id
+ end
+
+ def status_active?
+ self.status_id == ::Hex::Cms::Status::ACTIVE
+ end
+
+ def status_inactive?
+ self.status_id == ::Hex::Cms::Status::INACTIVE
+ end
+
+ def status_removed?
+ self.status_id == ::Hex::Cms::Status::REMOVED
+ end
+
+ end
+ end
+ end
+end
View
6 lib/hex/cms/version.rb
@@ -0,0 +1,6 @@
+# encoding: utf-8
+module Hex
+ module Cms
+ VERSION = "0.0.1"
+ end
+end
View
31 lib/hexcms-rails.rb
@@ -1,7 +1,30 @@
-require "hexcms-rails/version"
+# encoding: utf-8
+require "rails"
+require "hex/cms/version"
-module Hexcms
- module Rails
- # Your code goes here...
+# cms modules and model boilerplates
+require "hex/cms/locale_module"
+require "hex/cms/code_module"
+
+require "hex/cms/box_base"
+require "hex/cms/layout_base"
+require "hex/cms/menu_base"
+require "hex/cms/menu_route_base"
+require "hex/cms/page_base"
+
+require "hex/cms/status_module"
+
+require "hex/cms/controller_methods"
+require "hex/cms/cms_controller_base"
+
+module Hex
+ module Cms
+ class Engine < Rails::Engine
+ config.to_prepare do
+ # include our helpers
+ ApplicationController.helper(Hex::Cms::BoxHelper)
+ ApplicationController.helper(Hex::Cms::LinkHelper)
+ end
+ end
end
end
View
5 lib/hexcms-rails/version.rb
@@ -1,5 +0,0 @@
-module Hexcms
- module Rails
- VERSION = "0.0.1"
- end
-end
Please sign in to comment.
Something went wrong with that request. Please try again.