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 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
@@ -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
+ &nbsp;
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 &ntilde;, &oacute;, &eacute;, 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>