A Rails gem for a pluggable CMS frontend
Rich-CMS is a module of E9s (http://github.com/archan937/e9s) which provides a frontend for your CMS content.
Please check out an online demo of Rich-CMS at the Rich-CMS or E9s page at http://codehero.es.
Add Rich-CMS in Gemfile
as a gem dependency:
gem "rich_cms"
Run the following in your console to install with Bundler:
bundle install
Add Rich-CMS in environment.rb
as a gem dependency:
config.gem "rich_cms"
Run the following in your console:
sudo rake gems:install
1. Create your own Rich-CMS fork https://github.com/archan937/rich_cms/fork
2. Clone your Rich-CMS fork repository
git clone [email protected]:<your_username>/rich_cms.git && cd rich_cms
3. Setup your environment in order to use the GemSuit tests provided within Rich-CMS
gem install gem_suit && suit fit -v
4. Run tests (e.g. the GemSuit integration tests) as follows (make sure you have Firefox installed)
suit -v
Note: Running the GemSuit integration tests take in a couple of minutes as it will test 10 different environments:
Every GemSuit integration test starts with a blank Rails application, runs the required Rails generators and tests the front-end within Firefox with Capybara
- Rails 2 – Authlogic authenticated with Moneta in-memory storage
- Rails 2 – Devise authenticated with Moneta in-memory storage
- Rails 2 – non-authenticated with Moneta in-memory storage
- Rails 2 – non-authenticated with Moneta ActiveRecord storage
- Rails 2 – non-authenticated with Rich-i18n forgery (combined keys)
- Rails 3 – Authlogic authenticated with Moneta in-memory storage
- Rails 3 – Devise authenticated with Moneta in-memory storage
- Rails 3 – non-authenticated with Moneta in-memory storage
- Rails 3 – non-authenticated with Moneta ActiveRecord storage
- Rails 3 – non-authenticated with Rich-i18n forgery (combined keys)
To run unit tests in both Rails 2 and 3
suit test unit -v
Note: All tests are running successfully using RVM in combination with ruby-1.8.7-p334
, ree-1.8.7-2011.03
and ruby-1.9.2-p180
.
5. You can list all the command line commands with the following
suit help
6. Start either of the dummy Rails applications for development purposes
suit s
Note: This runs the Rails 3 dummy app
suit s -r2
7. Open the Rails application in your browser with http://localhost:3000/cms and log in with [email protected]
and testrichcms
.
8. Get on programming and send your pull request! ;)
Note: For more information about GemSuit, please visit its Github page.
Rich-CMS requires one entity:
- A model used for CMS content storage (which is powered with Moneta)
For (optional) authentication, Rich-CMS requires one of the following:
- A
Devise
authenticated admin model - An
Authlogic
authenticated admin model
Fortunately, Rich-CMS is provided with two Rails generators with which you can generate those entities.
Run the following in your console:
rails g rich:cms_admin -m
Note: At default, it will create a (Devise powered) User
model, the CreateUsers
migration and it will configure your routes.
You can alter the class name as follows:
rails g rich:cms_admin CodeHeroes::User -m
Note: Both generators have the -m
or --migrate
option which runs rake db:migrate
after creating the files.
Using Authlogic
You can use Authlogic by specifying the -a
or --authlogic
option:
rails g rich:cms_admin CodeHeroes::User -a -m
Note: As mentioned earlier, Devise is the default authentication logic. Having that said, you can explicitly specify Devise with the -d
or --devise
option.
Run the following in your console:
script/generate rich_cms_admin -m
Attention: The Devise
Rails generator code is (practically) a copy of the Devise 1.0.9 generator code. For there are problems calling the original Devise generators in Rails 2. See also this Stackoverflow issue.
Run the following in your console:
rails g rich:cms_content -m
Note: At default, it will create the CmsContent
model and CreateCmsContents
migration. You can alter the class name with the following:
rails g rich:cms_content CmsItem -m
Run the following in your console:
script/generate rich_cms_content -m
In case you have used the Rails generators, you can skip the Create required entities manually and go straight to Render Rich-CMS in your views.
Rich-CMS can be used without an authentication mechanism (which is the default by the way), you just have to open “/cms” in your browser and you are ready to go. But it is common to have authentication and Rich-CMS thus supports Devise and Authlogic.
Provide the authentication logic as a symbol (e.g. :devise
) and the authenticated class like this:
Rich::Cms::Auth.setup do |config| config.logic = :devise config.klass = "User" end
The following specifications are optional as Rich-Cms uses defaults:
:inputs
– default:[:email, :password]
The attributes used for the login panel of Rich-CMS:identifier
– default: based oninputs
The method used for displaying the identity of the current Rich-CMS admin (this is the first entry ofinputs
, so usually:email
):current_admin_method
– default: based onklass
The controller method used to retrieve the current Rich-CMS admin (e.g.current_user
when configuredUser
as authenticated class)
The storage mechanism of Rich-CMS content is powered with Moneta. Doing this gives you the freedom to choose the storage engine you want to use (e.g. Mongo, Redis, Tyrant, Memcache, DataMapper, ActiveRecord and many others).
Go to app/models
and create a file like the following (e.g. called Content
):
class Content include Rich::Cms::Content storage :memory end
In short: include the Rich::Cms::Content
module and specify which storage engine you want to use. And that was it actually!
Depending on your choice of storage engine, you will need to add some extra specs. See the Moneta repo for more documentation.
There are also additional class methods which you can call of course:
As already mentioned, Rich-CMS uses Moneta for an unified key / value store interface. But you can still let the key (identifier) consist out of multiple segments. Rich-CMS will concatenate the segments with a default delimiter ;
in the specified order within the class definition.
class Content include Rich::Cms::Content storage :memory identifiers :locale, :key # <= Rich-CMS will concatenate the identifier in this order end
As an example, :locale => :nl, :key => "application.index.header"
will result in "nl:application.index.header"
. You can change the delimiter by overriding the delimiter
method:
... identifiers :locale, :key def delimiter ";" # <= {:locale => :nl, :key => "application.index.header"} will result in "nl;application.index.header" end end
Note: You might want to check out rich/i18n_forgery.rb for further documentation (especially the protected method identity_hash_for
).
When it comes to rendering Rich-CMS content in the front-end, there are three points of attention regarding the Rich-CMS content:
- the CSS class of the Rich-CMS content class in question
- the Javascript function called before showing the edit form of a content item
- the Javascript function called after updating a content item
Rich-CMS uses the CSS class to match the corresponding Rich-CMS content class in the front-end. At default, the CSS class is based on the underscored content class name prefixed with "rcms_"
.
So Content
will result in “.rcms_content
” and Translation
in ".rcms_translation"
. When the class name starts with Cms
, it will be ignored. For instance, CmsContent
will result in ".rcms_content"
.
At default, the before_edit
hook is not assigned and the after_update
hook is assign to the Javascript function Rich.Cms.Editor.afterUpdate
.
A simple Rich-CMS class:
class Content include Rich:Cms::Content storage :memory css_class "custom_css_class" end
A slight more advanced example of a Rich-CMS class:
class Translation include Rich::Cms::Content storage :active_record, :key => :custom_key_column, :value => :custom_value_column identifiers :locale, :key configure do |config| # for a custom CSS class: configure "translation" do |config| config.before_edit "Rich.I18n.beforeEdit" config.after_update "Rich.I18n.afterUpdate" end end
Note: Please notice that Translation
uses ActiveRecord
as storage engine
Add the following line at the beginning of the <body>
tag:
<body> <%= rich_cms %> ... </body>
Rich-CMS requires a rendered DOM element provided with meta data of the content entry. Fortunately, you can call a helper method to render Rich-CMS content tags. It accepts the following arguments:
css_class
– The CSS class of the corresponding Rich-CMS content classidentifier
– The identifier of the Rich-CMS content entry (use a hash when dealing with a combined identifier)options
– Options used to customize the rendered content tag
Please take notice: The CSS class is only obligated when having more than one Rich-CMS content class defined!
>> key = "hello_world" => "hello_world" >> ActionView::Base.new.rich_cms_tag(key) => "<div class='rcms_content' data-store_key='hello_world' data-store_value='Hello world!'>Hello world!</div>"
Note: There is only one Rich-CMS content defined called Content
When using a combined key for content identification, just call it as follows:
>> ActionView::Base.new.rich_cms_tag({:key => key, :locale => I18n.locale}) => "<div class='rcms_content' data-store_key='nl:hello_world' data-store_value='Hallo wereld!'>Hallo wereld!</div>"
Note: In this case, the (only) Rich-CMS content class was defined with identifiers :locale, :key
When having multiple Rich-CMS content classes defined, pass the corresponding CSS class:
>> ActionView::Base.new.rich_cms_tag(".rcms_translation", {:key => key, :locale => I18n.locale}) => "<div class='rcms_translation' data-store_key='nl:hello_world' data-store_value='Hallo wereld!'>Hallo wereld!</div>"
The (optional) options hash can contain the following keys:
:as
– default: auto-determined:string
or:text
Specify the input type shown in the edit form (:string
for an input text,:text
for a textarea and:html
for a WYSIWYG HTML editor).:tag
– default: auto-determined:div
or:span
The HTML tag used for content items. You can also specify:none
as value: in that case, no HTML tag will be wrapped around the content when no admin is logged in (essential in case the extraspan
will bork the layout).:html
– default:{}
HTML attributes added to the content tag (e.g.:id
,:class
):locals
– default:nil
When assigned a Hash, Rich-CMS assumes the content concerns a Mustache template and will render it with the passedlocals
.:collection
– default:nil
When assigned an array, Rich-CMS assumes the content concerns a Mustache template and will render it with the (optional) passedlocals
and theattr_cmsable
attributes.
Note: The WYSIWYG editor used is the jQuery based CLEditor
... <%= rich_cms_tag ".rcms_content", "about_us_page", :as => :html %> <%= rich_cms_tag ".rcms_content", {:key => "welcome_text", :locale => I18n.locale}, :tag => :p %> <%= rich_cms_tag "application.index.welcome_text", :tag => :p, :html => {:class => "welcome", :style => "display: none"} %> ...
Note: The last statement passes when only one Rich-CMS content class is defined
The Rich-CMS content used:
header = Content.new :key => "welcome_header" header.value = "Hi, {{name}}!" header.save
The ERB template:
... <%= rich_cms_tag "welcome_header", :tag => :h1, :locals => {:name => "Vicky"} %> ...
The compiled HTML:
<h1> Hi, Vicky! </h1>
The Rich-CMS content and collection used:
class AttrCmsableContent # probably < ActiveRecord::Base attr_accessor :name # an attribute provided by ActiveRecord::Base attr_cmsable :name end @collection = %w(Vicky Johnny Paul).collect{|name| AttrCmsableContent.new.tap{|c| c.name = name}} header = Content.new :key => "welcome_header" header.value = "Hi, {{name}}!" header.save
The ERB template:
... <ul> <%= rich_cms_tag "welcome_header", :tag => :li, :collection => @collection %> <ul> ...
The compiled HTML:
<ul> <li>Hi, Vicky!</li> <li>Hi, Johnny!</li> <li>Hi, Paul!</li> </ul>
Open http://localhost:3000/cms, log in and start managing CMS content.
The update
action response of Rich::CmsController
consists of JSON data regarding the updated content item. The response will be passed to the after_update
Javascript function which contains a simple JSON hash. Its default is as follows:
{"__css_class__": "rcms_content", "__identifier__": {"store_key": "hello_world"}, "store_value": "Hello world!"}
Note: __css_class__
, __identifier__
and store_value
are always provided in the JSON data.
When specifying a custom after update Javascript function, you probably want to acquire more information than provided in the default JSON data. You can customize this by defining the to_rich_cms_response
method in the CMS content model class and the hash will be merged with the default response hash:
class Translation include Rich::Cms::Content storage :memory identifiers :locale, :key configure "translation" do |config| config.before_edit "Rich.I18n.beforeEdit" config.after_update "Rich.I18n.afterUpdate" end def to_tag(options = {}) super options.merge(:data => {:derivative_key => derivative_key}) end def to_rich_cms_response(params) {:translations => Hash[*params[:derivative_keys].split(";").uniq.collect{|x| [x, x.t]}.flatten]} end end
The JSON data returned will look like:
{"__css_class__": "translation", "__identifier__": {"store_key": "nl:word.user"}, "store_value": "gebruiker", "translations": {"users": "gebruikers"}}
For support, remarks and requests please mail me at [email protected].
This Rails gem depends on:
jQuery
http://jquery.com
GemSuit
https://github.com/archan937/gem_suit
Devise (optional)
http://github.com/plataformatec/devise
AuthLogic (optional)
http://github.com/binarylogic/authlogic
Moneta
https://github.com/wycats/moneta
SASS
http://sass-lang.com
Jzip
http://codehero.es/rails_gems_plugins/jzip
http://github.com/archan937/jzip
RaccoonTip
http://codehero.es/jquery_libraries/raccoon_tip
http://github.com/archan937/raccoon_tip
SeatHolder
http://codehero.es/jquery_libraries/seat_holder
http://github.com/archan937/seat_holder
CLEditor
http://premiumsoftware.net/cleditor/index.html
- Mark Mulder – @bitterzoet – http://ikbenbitterzoet.com
- Stephan Kaag – @stephankaag – http://hollandonrails.nl
- Jeroen Bulters – @bulters – http://bulte.rs
- Chris Obdam – @chrisobdam – http://holder.nl
- Johan Vermeulen – @johpie – http://www.prutz-lan.nl
- Peter Beers – http://holder.nl
- Add cache feature which uses the standard Rails cache
- Provide web pages management (e.g. Rich-Pages)
- Provide object management (e.g. articles and products)
- Provide file uploads (e.g. images)
- Check out compatibility with Devise 1.2 and 1.3
- Provide better conventions for content rendering
- Provide tools to use Textile, MarkDown
The all-in-one gem at – http://codehero.es/rails_gems_plugins/e9s – http://github.com/archan937/e9s
- Rich-Support
http://codehero.es
http://github.com/archan937/rich_support - Rich-CMS
http://codehero.es/rails_gems_plugins/rich_cms
http://github.com/archan937/rich_cms - Rich-i18n
http://codehero.es/rails_gems_plugins/rich_i18n
http://github.com/archan937/rich_i18n - Rich-pluralization
http://codehero.es/rails_gems_plugins/rich_pluralization
http://github.com/archan937/rich_pluralization
Copyright © 2011 Paul Engel, released under the MIT license
http://holder.nl – http://codehero.es – http://gettopup.com – http://twitter.com/archan937 – [email protected]
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.