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 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>