jakewendt-pages 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +55 -0
- data/app/controllers/locales_controller.rb +15 -0
- data/app/controllers/pages_controller.rb +138 -0
- data/app/models/page.rb +99 -0
- data/app/models/page_sweeper.rb +74 -0
- data/app/views/pages/_child.html.erb +23 -0
- data/app/views/pages/_form.html.erb +44 -0
- data/app/views/pages/_page.html.erb +35 -0
- data/app/views/pages/edit.html.erb +9 -0
- data/app/views/pages/index.html.erb +28 -0
- data/app/views/pages/new.html.erb +9 -0
- data/app/views/pages/show.html.erb +29 -0
- data/app/views/pages/translate.js.erb +42 -0
- data/config/routes.rb +9 -0
- data/generators/pages/USAGE +0 -0
- data/generators/pages/pages_generator.rb +66 -0
- data/generators/pages/templates/functional/locales_controller_test.rb +59 -0
- data/generators/pages/templates/functional/pages_controller_test.rb +248 -0
- data/generators/pages/templates/images/drag.gif +0 -0
- data/generators/pages/templates/javascripts/pages.js +39 -0
- data/generators/pages/templates/migrations/create_pages.rb +26 -0
- data/generators/pages/templates/stylesheets/page.css +17 -0
- data/generators/pages/templates/stylesheets/pages.css +22 -0
- data/generators/pages/templates/unit/page_test.rb +185 -0
- data/generators/pages/templates/unit/redcloth_extension_test.rb +64 -0
- data/lib/pages.rb +42 -0
- data/lib/pages/factories.rb +6 -0
- data/lib/pages/pending.rb +72 -0
- data/lib/pages/redcloth/formatters/html.rb +23 -0
- data/lib/pages/tasks.rb +1 -0
- metadata +349 -0
data/README.rdoc
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
= Pages
|
2
|
+
|
3
|
+
This is both a plugin engine and a basic rails app.
|
4
|
+
|
5
|
+
|
6
|
+
|
7
|
+
== ToDo
|
8
|
+
|
9
|
+
|
10
|
+
== Required Gem Sources
|
11
|
+
|
12
|
+
gem sources -a http://rubygems.org
|
13
|
+
gem sources -a http://gems.github.com
|
14
|
+
|
15
|
+
== Required Gems
|
16
|
+
|
17
|
+
* {assert_this_and_that}[http://github.com/jakewendt/assert_this_and_that]
|
18
|
+
* {ruby_extension}[http://github.com/jakewendt/ruby_extension] - modifications, updates and patches for ruby.
|
19
|
+
* rails ~> 2
|
20
|
+
* i18n =0.3.7
|
21
|
+
* jrails
|
22
|
+
* chronic
|
23
|
+
* ruby-hmac
|
24
|
+
* ssl_requirement
|
25
|
+
* ryanb-acts-as-list
|
26
|
+
* RedCloth
|
27
|
+
* thoughtbot-factory_girl
|
28
|
+
* jakewendt-rails_helpers
|
29
|
+
* jakewendt-calnet_authenticated
|
30
|
+
|
31
|
+
== Installation (as a plugin/engine)
|
32
|
+
|
33
|
+
|
34
|
+
== Testing (as an app)
|
35
|
+
|
36
|
+
rake db:migrate
|
37
|
+
rake db:fixtures:load
|
38
|
+
rake test
|
39
|
+
script/server
|
40
|
+
|
41
|
+
== Gemified with Jeweler
|
42
|
+
|
43
|
+
vi Rakefile
|
44
|
+
rake version:write
|
45
|
+
|
46
|
+
rake version:bump:patch
|
47
|
+
rake version:bump:minor
|
48
|
+
rake version:bump:major
|
49
|
+
|
50
|
+
rake gemspec
|
51
|
+
|
52
|
+
rake install
|
53
|
+
rake release
|
54
|
+
|
55
|
+
Copyright (c) 2010 [Jake Wendt], released under the MIT license
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class LocalesController < ApplicationController
|
2
|
+
|
3
|
+
skip_before_filter :login_required
|
4
|
+
skip_before_filter :build_menu_js
|
5
|
+
|
6
|
+
def show
|
7
|
+
session[:locale] = params[:id]
|
8
|
+
respond_to do |format|
|
9
|
+
format.html { redirect_to_referer_or_default(root_path) }
|
10
|
+
# format.js {}
|
11
|
+
format.js { render :text => "locale = '#{session[:locale]}'" }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
class PagesController < ApplicationController
|
2
|
+
|
3
|
+
skip_before_filter :login_required,
|
4
|
+
:only => [:show, :translate]
|
5
|
+
skip_before_filter :build_menu_js,
|
6
|
+
:only => [:translate]
|
7
|
+
|
8
|
+
before_filter :may_maintain_pages_required, :except => [:show, :translate]
|
9
|
+
before_filter :id_required, :only => [ :edit, :update, :destroy ]
|
10
|
+
before_filter :page_required, :only => :show
|
11
|
+
before_filter :build_submenu_js, :except => [:index, :order, :translate]
|
12
|
+
|
13
|
+
# caches partials from layout as well, which is too much
|
14
|
+
# caching still buggy
|
15
|
+
# if do cache layout, contains user links
|
16
|
+
# if don't cache layout, submenu goes missing
|
17
|
+
|
18
|
+
# caches_action saves to memory
|
19
|
+
# caches_page generates an actual file in public/
|
20
|
+
# it would probably require modifications to the
|
21
|
+
# page_sweeper's expire calls
|
22
|
+
caches_action :show #, :layout => false
|
23
|
+
# caches_page :show #, :layout => false
|
24
|
+
cache_sweeper :page_sweeper, :only => [:create, :update, :order, :destroy]
|
25
|
+
|
26
|
+
ssl_allowed :show, :translate
|
27
|
+
|
28
|
+
def order
|
29
|
+
# params[:pages].reverse.each { |id| Page.find(id).move_to_top }
|
30
|
+
# this doesn't even check for parents or anything
|
31
|
+
# making it faster, but potentially error prone.
|
32
|
+
params[:pages].each_with_index { |id,index|
|
33
|
+
Page.find(id).update_attribute(:position, index+1 ) }
|
34
|
+
redirect_to pages_path(:parent_id=>params[:parent_id])
|
35
|
+
end
|
36
|
+
|
37
|
+
def translate
|
38
|
+
respond_to do |format|
|
39
|
+
format.js {}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def show
|
44
|
+
end
|
45
|
+
|
46
|
+
def index
|
47
|
+
@page_title = "CCLS Pages"
|
48
|
+
params[:parent_id] = nil if params[:parent_id].blank?
|
49
|
+
@pages = Page.all(:conditions => { :parent_id => params[:parent_id] })
|
50
|
+
end
|
51
|
+
|
52
|
+
def new
|
53
|
+
@page_title = "Create New CCLS Page"
|
54
|
+
@page = Page.new(:parent_id => params[:parent_id])
|
55
|
+
end
|
56
|
+
|
57
|
+
def edit
|
58
|
+
@page_title = "Edit CCLS Page #{@page.title(session[:locale])}"
|
59
|
+
end
|
60
|
+
|
61
|
+
def create
|
62
|
+
@page = Page.new(params[:page])
|
63
|
+
@page.save!
|
64
|
+
flash[:notice] = 'Page was successfully created.'
|
65
|
+
redirect_to(@page)
|
66
|
+
rescue ActiveRecord::RecordInvalid
|
67
|
+
flash.now[:error] = "There was a problem creating the page"
|
68
|
+
render :action => "new"
|
69
|
+
end
|
70
|
+
|
71
|
+
def update
|
72
|
+
@page.update_attributes!(params[:page])
|
73
|
+
flash[:notice] = 'Page was successfully updated.'
|
74
|
+
redirect_to(@page)
|
75
|
+
rescue ActiveRecord::RecordInvalid
|
76
|
+
flash.now[:error] = "There was a problem updating the page."
|
77
|
+
render :action => "edit"
|
78
|
+
end
|
79
|
+
|
80
|
+
def destroy
|
81
|
+
@page.destroy
|
82
|
+
redirect_to(pages_path)
|
83
|
+
end
|
84
|
+
|
85
|
+
protected
|
86
|
+
|
87
|
+
def id_required
|
88
|
+
if !params[:id].blank? and Page.exists?(params[:id])
|
89
|
+
@page = Page.find(params[:id])
|
90
|
+
else
|
91
|
+
access_denied("Valid page id required!", pages_path)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Put this in a separate before_filter so that
|
96
|
+
# another before_filter can access @page
|
97
|
+
def page_required
|
98
|
+
if params[:path]
|
99
|
+
@page = Page.by_path("/#{params[:path].join('/')}")
|
100
|
+
raise ActiveRecord::RecordNotFound if @page.nil?
|
101
|
+
else
|
102
|
+
@page = Page.find(params[:id])
|
103
|
+
end
|
104
|
+
@page_title = @page.title(session[:locale])
|
105
|
+
if @page.is_home? && class_exists?('HomePagePic')
|
106
|
+
@hpp = HomePagePic.random_active()
|
107
|
+
end
|
108
|
+
rescue ActiveRecord::RecordNotFound
|
109
|
+
flash_message = "Page not found with "
|
110
|
+
flash_message << (( params[:id].blank? ) ? "path '/#{params[:path].join('/')}'" : "ID #{params[:id]}")
|
111
|
+
flash.now[:error] = flash_message
|
112
|
+
end
|
113
|
+
|
114
|
+
def build_submenu_js
|
115
|
+
if @page && !@page.root.children.empty?
|
116
|
+
js = "" <<
|
117
|
+
"if ( typeof(translatables) == 'undefined' ){\n" <<
|
118
|
+
" var translatables = [];\n" <<
|
119
|
+
"}\n"
|
120
|
+
js << "tmp={tag:'#current_root',locales:{}};\n"
|
121
|
+
%w( en es ).each do |locale|
|
122
|
+
js << "tmp.locales['#{locale}']='#{@page.root.menu(locale)}'\n"
|
123
|
+
end
|
124
|
+
js << "translatables.push(tmp);\n"
|
125
|
+
@page.root.children.each do |child|
|
126
|
+
js << "tmp={tag:'#menu_#{dom_id(child)}',locales:{}};\n"
|
127
|
+
%w( en es ).each do |locale|
|
128
|
+
js << "tmp.locales['#{locale}']='#{child.menu(locale)}'\n"
|
129
|
+
end
|
130
|
+
js << "translatables.push(tmp);\n"
|
131
|
+
end
|
132
|
+
@template.content_for :head do
|
133
|
+
@template.javascript_tag js
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
data/app/models/page.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
# == requires
|
2
|
+
# * path ( unique, > 1 char and starts with a / )
|
3
|
+
# * menu ( unique and > 3 chars )
|
4
|
+
# * title ( > 3 chars )
|
5
|
+
# * body ( > 3 chars )
|
6
|
+
#
|
7
|
+
# == named_scope(s)
|
8
|
+
# * not_home (returns those pages where path is not just '/')
|
9
|
+
# * roots
|
10
|
+
#
|
11
|
+
# uses acts_as_list for parent / child relationship. As this
|
12
|
+
# is only a parent and child and no deeper, its ok. If it
|
13
|
+
# were to get any deeper, the list should probably be changed
|
14
|
+
# to something like a nested set.
|
15
|
+
class Page < ActiveRecord::Base
|
16
|
+
default_scope :order => :position
|
17
|
+
|
18
|
+
acts_as_list :scope => :parent_id
|
19
|
+
# acts_as_list :scope => "parent_id \#{(parent_id.nil?)?'IS NULL':'= parent_id'} AND locale = '\#{locale}'"
|
20
|
+
|
21
|
+
validates_length_of :path, :minimum => 1
|
22
|
+
validates_format_of :path, :with => /^\//
|
23
|
+
validates_length_of :menu_en, :minimum => 4
|
24
|
+
validates_length_of :title_en, :minimum => 4
|
25
|
+
validates_length_of :body_en, :minimum => 4
|
26
|
+
validates_uniqueness_of :menu_en
|
27
|
+
validates_uniqueness_of :path
|
28
|
+
|
29
|
+
belongs_to :parent, :class_name => 'Page'
|
30
|
+
has_many :children, :class_name => 'Page', :foreign_key => 'parent_id',
|
31
|
+
:dependent => :nullify
|
32
|
+
|
33
|
+
named_scope :roots, :conditions => {
|
34
|
+
:parent_id => nil, :hide_menu => false }
|
35
|
+
|
36
|
+
named_scope :hidden, :conditions => {
|
37
|
+
:hide_menu => true }
|
38
|
+
|
39
|
+
named_scope :not_home, :conditions => [ "path != '/'" ]
|
40
|
+
|
41
|
+
attr_accessible :path, :parent_id, :hide_menu,
|
42
|
+
:menu, :menu_en, :menu_es,
|
43
|
+
:title, :title_en, :title_es,
|
44
|
+
:body, :body_en, :body_es
|
45
|
+
|
46
|
+
before_validation :adjust_path
|
47
|
+
|
48
|
+
def adjust_path
|
49
|
+
unless self.path.nil?
|
50
|
+
# remove any duplicate /'s
|
51
|
+
# self.path = path.gsub(/\/+/,'/')
|
52
|
+
self.path.gsub!(/\/+/,'/')
|
53
|
+
|
54
|
+
# add leading / if none
|
55
|
+
# self.path = path.downcase
|
56
|
+
self.path.downcase!
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# named_scopes ALWAYS return an "Array"
|
61
|
+
# so if ONLY want one, MUST use a method.
|
62
|
+
# by_path returns the one(max) page that
|
63
|
+
# matches the given path.
|
64
|
+
def self.by_path(path)
|
65
|
+
page = find(:first,
|
66
|
+
:conditions => {
|
67
|
+
:path => path.downcase
|
68
|
+
}
|
69
|
+
)
|
70
|
+
end
|
71
|
+
|
72
|
+
def root
|
73
|
+
page = self
|
74
|
+
until page.parent == nil
|
75
|
+
page = page.parent
|
76
|
+
end
|
77
|
+
page
|
78
|
+
end
|
79
|
+
|
80
|
+
def is_home?
|
81
|
+
self.path == "/"
|
82
|
+
end
|
83
|
+
|
84
|
+
# Virtual attributes
|
85
|
+
%w( menu title body ).each do |attr|
|
86
|
+
define_method "#{attr}" do |*args|
|
87
|
+
r = send("#{attr}_#{args[0]||'en'}")
|
88
|
+
(r.blank?) ? send("#{attr}_en") : r
|
89
|
+
end
|
90
|
+
define_method "#{attr}=" do |new_val|
|
91
|
+
self.send("#{attr}_en=",new_val)
|
92
|
+
end
|
93
|
+
# attr_accessible attr.to_sym
|
94
|
+
# %w( en es ).each do |lang|
|
95
|
+
# attr_accessible "#{attr}_#{lang}".to_sym
|
96
|
+
# end
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# While sweepers are model observers, they only seem
|
2
|
+
# to be so within the scope of a given controller.
|
3
|
+
# If the 'cache_sweeper' line is not given in the
|
4
|
+
# controller, these observers don't see it.
|
5
|
+
# In addition, saving or destroying a page outside
|
6
|
+
# of the scope of the controller (ie. from console)
|
7
|
+
# will also go unnoticed.
|
8
|
+
#
|
9
|
+
# Interestingly, in unit/model testing, this fails
|
10
|
+
# as request is nil. This means that I don't quite
|
11
|
+
# understand how this works. Perhaps because the cache
|
12
|
+
# is in memory and not a file, the server and console
|
13
|
+
# are completely separate. Destroying in the console
|
14
|
+
# doesn't effect the server's cache. That makes sense.
|
15
|
+
# So then. How to get the host_with_port without a
|
16
|
+
# request then?
|
17
|
+
class PageSweeper < ActionController::Caching::Sweeper
|
18
|
+
observe Page
|
19
|
+
|
20
|
+
# After saving (creating or updating) a page,
|
21
|
+
# expire any associated caches.
|
22
|
+
def after_save(page)
|
23
|
+
expire_cache(page)
|
24
|
+
end
|
25
|
+
|
26
|
+
# After destroying a page, expire any associated
|
27
|
+
# caches.
|
28
|
+
def after_destroy(page)
|
29
|
+
expire_cache(page)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Always expire the menu cache. This may not
|
33
|
+
# be necessary as it only relies on the menu,
|
34
|
+
# path and position attributes which may have
|
35
|
+
# remained unchanged. Also expire the "show"
|
36
|
+
# action cache. As the pages are rarely accessed
|
37
|
+
# via /pages/:id, we need to expire the special
|
38
|
+
# routes used by the catch-all route. In the
|
39
|
+
# real world, this works just fine, but in testing
|
40
|
+
# the app tries to expire cache when in unit tests
|
41
|
+
# as well as functional tests. There is no
|
42
|
+
# "request" in a unit test causing failure and
|
43
|
+
# my usage of ".try"
|
44
|
+
def expire_cache(page)
|
45
|
+
# Please note that the "views/" prefix is
|
46
|
+
# internal to rails. Don't meddle with it.
|
47
|
+
|
48
|
+
# Expired fragment: views/page_menu (0.0ms)
|
49
|
+
expire_fragment 'page_menu'
|
50
|
+
|
51
|
+
# We don't really access the pages via :id
|
52
|
+
# but they can be so we need to deal with the cache.
|
53
|
+
# Expired fragment: views/dev.sph.berkeley.edu:3000/pages/1 (0.0ms)
|
54
|
+
# Expired fragment: views/dev.sph.berkeley.edu:3000/pages/5 (0.0ms)
|
55
|
+
expire_action
|
56
|
+
|
57
|
+
# Expired fragment: views/dev.sph.berkeley.edu:3000/alpha (0.0ms)
|
58
|
+
# Expired fragment: views/dev.sph.berkeley.edu:3000/ (0.0ms)
|
59
|
+
# This fails for the home page as "/" is
|
60
|
+
# called "index" by the server
|
61
|
+
# NEEDS to change page.path to /index for home page
|
62
|
+
# be views/dev.sph.berkeley.edu:3000/index !
|
63
|
+
page_path = ( page.path == "/" ) ? "/index" : page.path
|
64
|
+
# expire_fragment "#{request.host_with_port}#{page_path}"
|
65
|
+
expire_fragment "#{request.try(:host_with_port)}#{page_path}"
|
66
|
+
|
67
|
+
# In production, the page caches weren't expiring
|
68
|
+
# Adding the relative_url_root should fix it.
|
69
|
+
page_path = ActionController::Base.relative_url_root.to_s + page_path
|
70
|
+
expire_fragment "#{request.try(:host_with_port)}#{page_path}"
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
<% content_tag_for( :tr, child ) do %>
|
2
|
+
<td class='path'>
|
3
|
+
<%= link_to child.path, child.path -%>
|
4
|
+
</td>
|
5
|
+
<td class='menu'>
|
6
|
+
<%= link_to child.menu(session[:locale]), child -%>
|
7
|
+
</td>
|
8
|
+
<td class='title'>
|
9
|
+
<%= link_to child.title(session[:locale]), child -%>
|
10
|
+
</td>
|
11
|
+
<td class='manage'>
|
12
|
+
<%= link_to "Edit", edit_page_path(child) -%>
|
13
|
+
|
14
|
+
<%= link_to "Destroy", page_path(child),
|
15
|
+
:confirm => "Delete '#{child.title(session[:locale])}'?\nAre you sure?",
|
16
|
+
:method => :delete -%>
|
17
|
+
</td>
|
18
|
+
<!--
|
19
|
+
<td class='children'>
|
20
|
+
<%#= render :partial => 'child', :collection => child.children %>
|
21
|
+
</td>
|
22
|
+
-->
|
23
|
+
<% end %><!-- class='child' -->
|
@@ -0,0 +1,44 @@
|
|
1
|
+
<% stylesheets('page') %>
|
2
|
+
|
3
|
+
<fieldset id='page'>
|
4
|
+
<legend>Page</legend>
|
5
|
+
|
6
|
+
<div class='parent'>
|
7
|
+
<b>Parent?</b> (Is this a main or sub menu page?)<br/>
|
8
|
+
<%= f.collection_select :parent_id, Page.all(:order => "menu_en"),
|
9
|
+
:id, :menu, :include_blank => true %>
|
10
|
+
</div>
|
11
|
+
|
12
|
+
<div class='hide_menu'>
|
13
|
+
<br/>
|
14
|
+
<%= f.check_box :hide_menu %>
|
15
|
+
<%= f.label :hide_menu, "Hide in menu system" %>
|
16
|
+
</div>
|
17
|
+
|
18
|
+
<div class='fields'>
|
19
|
+
|
20
|
+
<p><b>Path:</b> (this is the path in the url. ie. /help/login )</p>
|
21
|
+
<%= f.text_field :path %>
|
22
|
+
|
23
|
+
<p><b>Title</b> (this is the text on the top of the browser)</p>
|
24
|
+
English:<%= f.text_field :title_en %>
|
25
|
+
Spanish:<%= f.text_field :title_es %>
|
26
|
+
|
27
|
+
<p><b>Menu</b> (this is the text on the menu)</p>
|
28
|
+
English:<%= f.text_field :menu_en %>
|
29
|
+
Spanish:<%= f.text_field :menu_es %>
|
30
|
+
|
31
|
+
<p><b>Body</b> (uses sanitized <%= link_to 'textile', 'http://redcloth.org', :target => 'new' %>)</p>
|
32
|
+
English:<%= f.text_area :body_en %>
|
33
|
+
Spanish:<%= f.text_area :body_es %>
|
34
|
+
</div><!-- fields -->
|
35
|
+
|
36
|
+
<p class='note'>
|
37
|
+
For special characters, like ñ, ó, é, etc., use the 'friendly code' from
|
38
|
+
<%= link_to "About.com", "http://webdesign.about.com/library/bl_htmlcodes.htm", :target => 'new' %>
|
39
|
+
or 'Entity Name' from
|
40
|
+
<%= link_to "W3Schools.com", "http://www.w3schools.com/tags/ref_entities.asp", :target => 'new' %>.
|
41
|
+
The 'Numerical Code', 'Hex Code' or 'Entity Number' work just as well, but aren't as clear.
|
42
|
+
</p>
|
43
|
+
|
44
|
+
</fieldset>
|