jakewendt-pages 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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>
|