sethyates-content_manager 0.4.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +253 -1
- data/VERSION +1 -1
- data/content_manager.gemspec +38 -2
- data/doc/created.rid +1 -0
- data/doc/files/README_rdoc.html +487 -0
- data/doc/fr_class_index.html +26 -0
- data/doc/fr_file_index.html +27 -0
- data/doc/fr_method_index.html +26 -0
- data/doc/index.html +24 -0
- data/doc/rdoc-style.css +208 -0
- data/generators/component_scaffold/USAGE +28 -0
- data/generators/component_scaffold/component_scaffold_generator.rb +84 -0
- data/generators/component_scaffold/templates/controller.rb +85 -0
- data/generators/component_scaffold/templates/model.rb +5 -0
- data/generators/component_scaffold/templates/style.css +1 -0
- data/generators/component_scaffold/templates/view_edit.html.erb +13 -0
- data/generators/component_scaffold/templates/view_index.html.erb +22 -0
- data/generators/component_scaffold/templates/view_new.html.erb +12 -0
- data/generators/component_scaffold/templates/view_show.html.erb +3 -0
- data/generators/content_scaffold/USAGE +28 -0
- data/generators/content_scaffold/content_scaffold_generator.rb +83 -0
- data/generators/content_scaffold/templates/controller.rb +85 -0
- data/generators/content_scaffold/templates/model.rb +8 -0
- data/generators/content_scaffold/templates/view_edit.html.erb +13 -0
- data/generators/content_scaffold/templates/view_index.html.erb +22 -0
- data/generators/content_scaffold/templates/view_new.html.erb +12 -0
- data/generators/content_scaffold/templates/view_show.html.erb +8 -0
- data/lib/component.rb +28 -0
- data/lib/content/adapters/base.rb +79 -0
- data/lib/content/adapters/cabinet_adapter.rb +62 -0
- data/lib/content/adapters/tyrant_adapter.rb +73 -0
- data/lib/content/item.rb +178 -0
- data/lib/content/item_association_class_methods.rb +148 -0
- data/lib/content/item_class_methods.rb +71 -0
- data/lib/content/item_dirty_methods.rb +171 -0
- data/lib/content/item_finder_class_methods.rb +203 -0
- data/lib/content/manager.rb +105 -0
- data/lib/content/sublayout.rb +44 -0
- data/lib/content/template.rb +24 -0
- metadata +38 -2
data/README.rdoc
CHANGED
@@ -1,9 +1,261 @@
|
|
1
1
|
= Content::Manager
|
2
2
|
|
3
|
-
|
3
|
+
== Overview
|
4
|
+
|
5
|
+
The Content::Manager plugin provides Content Management capability for Rails applications. Content::Manager is built on top of a Tokyo Tyrant (or Tokyo Cabinet) schema-less table database.
|
6
|
+
Using standard Rails functionality of models, views and controllers with some additional "glue", Content::Manager makes it straight-forward to build component-based, content-managed Rails applications.
|
7
|
+
|
8
|
+
== How it works
|
9
|
+
|
10
|
+
The Content Manager is invoked by the final route in the routes file:
|
11
|
+
|
12
|
+
map.content_item '*content_item_url', :controller => 'content', :action => 'show'
|
13
|
+
|
14
|
+
This route tells Rails to pass any URL (in the content_item_url parameter) to the show action of the ContentController.
|
15
|
+
|
16
|
+
When the show action in the ContentController is invoked (inherited from Content::Manager), the before_filter :current_content_item in the ApplicationController is invoked first. This causes the Content::Item specified by the URL in content_item_url to be loaded using Content::Item.find_by_url params[:content_item_url].
|
17
|
+
|
18
|
+
Back in the ContentController#show action, the current_content_item is checked if it is nil, if it is missing a template or if its template is missing a sublayout. In any of these cases, a 404 Not Found error is rendered (specified in app/views/errors/error404.html.erb).
|
19
|
+
|
20
|
+
Assuming we have a valid Content::Item with a template and a sublayout, then the containers are rendered (using prerender_containers, content_for, render_container and render_component in Content::Manager). The contents of each container are stored for later use by yield within the Sublayout. Finally, the Sublayout is rendered as a template using render :template. At this point, no layout is specified, so Sublayouts have to provide the full HTML. This may change in the future though.
|
21
|
+
|
22
|
+
The Content::Manager#render_component method requires special mention. This method operates by creating a new Rack request and handing the request off to the ActionController::Routing::Routes table. Any error from here is raised as a RuntimeError with the message being the full HTML returned from the error. In the case of Content::Manager, any errors from the components is returned as the page error.
|
23
|
+
|
24
|
+
== Creating a Content Item
|
25
|
+
1. Create the content_scaffold.
|
26
|
+
|
27
|
+
$ script/generate content_scaffold Video duration:string
|
28
|
+
exists app/models/content
|
29
|
+
exists app/controllers/content
|
30
|
+
create app/views/content/videos
|
31
|
+
exists app/views/errors
|
32
|
+
exists app/views/sublayouts
|
33
|
+
exists public/stylesheets/content
|
34
|
+
create app/views/content/videos/index.html.erb
|
35
|
+
create app/views/content/videos/show.html.erb
|
36
|
+
create app/views/content/videos/new.html.erb
|
37
|
+
create app/views/content/videos/edit.html.erb
|
38
|
+
create app/controllers/content/videos_controller.rb
|
39
|
+
route map.resources :videos
|
40
|
+
create app/models/content/video.rb
|
41
|
+
|
42
|
+
2. Edit the model (app/models/content/video.rb) and add fields
|
43
|
+
|
44
|
+
You can use the field method or fields method. You can optionally specify the type of the field as the second parameter to field. The following methods are available: field_name, fieldname=, fieldname_changed?, fieldname_change, fieldname_was.
|
45
|
+
|
46
|
+
class Content::Video < Content::Item
|
47
|
+
field :duration
|
48
|
+
end
|
49
|
+
|
50
|
+
3. Add associations (has_many, has_one, belongs_to)
|
51
|
+
|
52
|
+
[has_many] creates a one-to-many association implemented as an array of ID’s. The following methods are available: singular_association_ids, singular_association_ids=, association, association=.
|
53
|
+
[has_one] creates a one-to-one association implemented as the ID of the other item. The following methods are available: association_id, association_id=, association, association=.
|
54
|
+
[belongs_to] creates a many-to-one association implemented as the ID of the other item. The following methods are available: association_id, association_id=, association, association=.
|
55
|
+
|
56
|
+
class Content::Video < Content::Item
|
57
|
+
field :heading
|
58
|
+
field :duration
|
59
|
+
belongs_to :parent_section
|
60
|
+
end
|
61
|
+
|
62
|
+
4. Add validations. All of the standard ActiveRecord validations are available.
|
63
|
+
|
64
|
+
class Content::Video < Content::Item
|
65
|
+
field :heading
|
66
|
+
field :duration
|
67
|
+
belongs_to :parent_section
|
68
|
+
validates_presence_of :heading
|
69
|
+
end
|
70
|
+
|
71
|
+
5. Set any indexes required.
|
72
|
+
|
73
|
+
class Content::Video < Content::Item
|
74
|
+
field :heading
|
75
|
+
field :duration
|
76
|
+
belongs_to :parent_section
|
77
|
+
validates_presence_of :heading
|
78
|
+
index :heading, :lexical
|
79
|
+
end
|
80
|
+
|
81
|
+
6. Edit the routes file to fix the default route created. In other words:
|
82
|
+
|
83
|
+
ActionController::Routing::Routes.draw do |map|
|
84
|
+
map.resources :videos # << note it is misplaced
|
85
|
+
|
86
|
+
# Content =======================================================
|
87
|
+
map.namespace :content do |content|
|
88
|
+
# Core CMS
|
89
|
+
content.resources :sublayouts, :only => [:index, :show]
|
90
|
+
content.resources :components, :only => [:index, :show]
|
91
|
+
content.resources :templates
|
92
|
+
|
93
|
+
# Extensions
|
94
|
+
content.resources :articles
|
95
|
+
content.resources :photo_galleries
|
96
|
+
content.resources :photos
|
97
|
+
end
|
98
|
+
# END Content
|
99
|
+
|
100
|
+
...
|
101
|
+
|
102
|
+
becomes:
|
103
|
+
|
104
|
+
ActionController::Routing::Routes.draw do |map|
|
105
|
+
|
106
|
+
# Content =======================================================
|
107
|
+
map.namespace :content do |content|
|
108
|
+
# Core CMS
|
109
|
+
content.resources :sublayouts, :only => [:index, :show]
|
110
|
+
content.resources :components, :only => [:index, :show]
|
111
|
+
content.resources :templates
|
112
|
+
|
113
|
+
# Extensions
|
114
|
+
content.resources :videos # << it should go here
|
115
|
+
content.resources :articles
|
116
|
+
content.resources :photo_galleries
|
117
|
+
content.resources :photos
|
118
|
+
end
|
119
|
+
# END Content
|
120
|
+
|
121
|
+
...
|
122
|
+
|
123
|
+
7. Edit your controller and views as for any normal Rails app. The controller should typically redirect to the index action in the create and update actions instead of to the show action. In other words:
|
124
|
+
|
125
|
+
format.html { redirect_to(@video) }
|
126
|
+
|
127
|
+
becomes
|
128
|
+
|
129
|
+
format.html { redirect_to(content_videos_url) }
|
130
|
+
|
131
|
+
== Using Content Items
|
132
|
+
|
133
|
+
Once you have created your Content::Item, you can use most of the standard ActiveRecord finder methods:
|
134
|
+
|
135
|
+
section = Content::Section.new()
|
136
|
+
section.heading = "My Heading"
|
137
|
+
section.body = "My Body"
|
138
|
+
section.new_record? # => true
|
139
|
+
section.save
|
140
|
+
section.new_record? # => false
|
141
|
+
|
142
|
+
section = Content::Section.new(:heading => "My Heading", :body => "My Body")
|
143
|
+
section.save
|
144
|
+
|
145
|
+
section = Content::Section.new() do |sect|
|
146
|
+
sect.heading = "My Heading"
|
147
|
+
sect.body = "My Body"
|
148
|
+
end
|
149
|
+
section.save
|
150
|
+
|
151
|
+
Content::Section.find_by_id(id)
|
152
|
+
Content::Section.find id
|
153
|
+
|
154
|
+
Content::Section.first
|
155
|
+
Content::Section.find(:first)
|
156
|
+
Content::Section.find(:first, :conditions => { :user_name => user_name })
|
157
|
+
Content::Section.find(:first, :order => :created_on, :offset => 5)
|
158
|
+
|
159
|
+
Content::Section.last
|
160
|
+
Content::Section.find(:last)
|
161
|
+
Content::Section.find(:last, :conditions => { :user_name => user_name })
|
162
|
+
Content::Section.find(:last, :order => :created_on, :offset => 5)
|
163
|
+
|
164
|
+
Content::Section.all
|
165
|
+
Content::Section.find(:all)
|
166
|
+
Content::Section.find(:all, :conditions => { :friends => "Bob" })
|
167
|
+
Content::Section.find(:all, :offset => 10, :limit => 10)
|
168
|
+
|
169
|
+
Content::Section.find_by_url "/url"
|
170
|
+
Content::Section.find_by_url_and_status "/url", "active"
|
171
|
+
|
172
|
+
== Creating a Sublayout
|
173
|
+
|
174
|
+
A sublayout is a standard Rails layout file, except it is created in the apps/views/sublayouts folder instead of apps/views/layouts. The sublayout accesses the contents of the containers as specified in the Template by yielding the container name. For example, to insert the contents of the "left" container, simply yield :left. Note that yield without an argument is the same as yield :contents as it accesses the "contents" container.
|
175
|
+
|
176
|
+
== Creating a Template
|
177
|
+
|
178
|
+
A Template joins a Sublayout together with the contents if the Sublayout’s containers. This allows the contents of the containers to be dynamically changed without changing the code of the Sublayout. A Template is just a Content::Item which has two interesting fields: heading (the name of the Template), sublayout (the relative path of the sublayout within the app/views/sublayouts folder). In addition to these standard fields, fields named after the containers in the sublayout are also created. For example, if the sublayout has the following containers [:left, :contents, :right], then there will be "left", "contents" and "right" fields in the Template. The value of each field is an array of the components for that container.
|
179
|
+
|
180
|
+
== Creating a Component
|
181
|
+
|
182
|
+
1. Create the component_scaffold.
|
183
|
+
|
184
|
+
$ script/generate component_scaffold VideoPlayer
|
185
|
+
exists app/models/components
|
186
|
+
exists app/controllers/components
|
187
|
+
create app/views/components/video_players
|
188
|
+
exists public/stylesheets/components
|
189
|
+
create app/views/components/video_players/index.html.erb
|
190
|
+
create app/views/components/video_players/show.html.erb
|
191
|
+
create app/views/components/video_players/new.html.erb
|
192
|
+
create app/views/components/video_players/edit.html.erb
|
193
|
+
create public/stylesheets/components/video_player.css
|
194
|
+
create app/controllers/components/video_players_controller.rb
|
195
|
+
route map.resources :video_players
|
196
|
+
create app/models/components/video_player.rb
|
197
|
+
|
198
|
+
2. Edit the model (app/models/components/video_player.rb).
|
199
|
+
|
200
|
+
class Components::VideoPlayer < Content::Item
|
201
|
+
end
|
202
|
+
|
203
|
+
3. As you can see from above, the component is simply another Content::Item, so all of the capabilities covered in creating a Content::Item apply.
|
204
|
+
|
205
|
+
4. Edit the routes file to fix the default route created. In other words:
|
206
|
+
|
207
|
+
ActionController::Routing::Routes.draw do |map|
|
208
|
+
map.resources :video_players # << note it is misplaced
|
209
|
+
|
210
|
+
# Components ===================================================
|
211
|
+
map.namespace :components do |component|
|
212
|
+
# Core CMS
|
213
|
+
component.resources :containers, :only => [:show]
|
214
|
+
|
215
|
+
# Extensions
|
216
|
+
component.resources :section_heros
|
217
|
+
end
|
218
|
+
# END Components
|
219
|
+
...
|
220
|
+
|
221
|
+
becomes:
|
222
|
+
|
223
|
+
ActionController::Routing::Routes.draw do |map|
|
224
|
+
# Components ===================================================
|
225
|
+
map.namespace :components do |component|
|
226
|
+
# Core CMS
|
227
|
+
component.resources :containers, :only => [:show]
|
228
|
+
|
229
|
+
# Extensions
|
230
|
+
component.resources :section_heros
|
231
|
+
component.resources :video_players # << it goes here
|
232
|
+
# END Components
|
233
|
+
...
|
234
|
+
|
235
|
+
5. Edit your controller and views as for any normal Rails app. The controller should typically redirect to the index action in the create and update actions instead of to the show action. In other words:
|
236
|
+
|
237
|
+
format.html { redirect_to(@video_player) }
|
238
|
+
|
239
|
+
becomes
|
240
|
+
|
241
|
+
format.html { redirect_to(components_video_player_url) }
|
242
|
+
|
243
|
+
6. The show view is the view that will be embedded within pages. The standard component layout (app/layouts/components.html.erb) will wrap the component in a <div> with the "component" class and a class with the name of the component model (in this case "video_player"), as follows:
|
244
|
+
|
245
|
+
<div id="video_player_6253" class="video_player component">
|
246
|
+
<!-- component body goes here -->
|
247
|
+
</div>
|
248
|
+
|
249
|
+
This allows for easy styling of the specific component and for all components.
|
250
|
+
|
251
|
+
7. Edit the component stylesheet public/stylesheets/components/video_player.css. Put any styles specific to this component in this stylesheet. Don't worry if you think this step will create a problem at runtime, as all of these stylesheets are combined at production time into a single stylesheet.
|
252
|
+
|
253
|
+
.video_player {}
|
4
254
|
|
5
255
|
== Copyright
|
6
256
|
|
7
257
|
Copyright (c) 2009 Seth Yates
|
258
|
+
|
8
259
|
Copyright (c) 2009 Independent Digital Media Pty Ltd
|
260
|
+
|
9
261
|
See LICENSE for details.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
1.0.0
|
data/content_manager.gemspec
CHANGED
@@ -2,11 +2,11 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{content_manager}
|
5
|
-
s.version = "0.
|
5
|
+
s.version = "1.0.0"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = ["Seth Yates"]
|
9
|
-
s.date = %q{2009-06-
|
9
|
+
s.date = %q{2009-06-30}
|
10
10
|
s.email = %q{syates@grandcentralmedia.com.au}
|
11
11
|
s.extra_rdoc_files = [
|
12
12
|
"LICENSE",
|
@@ -20,7 +20,43 @@ Gem::Specification.new do |s|
|
|
20
20
|
"Rakefile",
|
21
21
|
"VERSION",
|
22
22
|
"content_manager.gemspec",
|
23
|
+
"doc/created.rid",
|
24
|
+
"doc/files/README_rdoc.html",
|
25
|
+
"doc/fr_class_index.html",
|
26
|
+
"doc/fr_file_index.html",
|
27
|
+
"doc/fr_method_index.html",
|
28
|
+
"doc/index.html",
|
29
|
+
"doc/rdoc-style.css",
|
30
|
+
"generators/component_scaffold/USAGE",
|
31
|
+
"generators/component_scaffold/component_scaffold_generator.rb",
|
32
|
+
"generators/component_scaffold/templates/controller.rb",
|
33
|
+
"generators/component_scaffold/templates/model.rb",
|
34
|
+
"generators/component_scaffold/templates/style.css",
|
35
|
+
"generators/component_scaffold/templates/view_edit.html.erb",
|
36
|
+
"generators/component_scaffold/templates/view_index.html.erb",
|
37
|
+
"generators/component_scaffold/templates/view_new.html.erb",
|
38
|
+
"generators/component_scaffold/templates/view_show.html.erb",
|
39
|
+
"generators/content_scaffold/USAGE",
|
40
|
+
"generators/content_scaffold/content_scaffold_generator.rb",
|
41
|
+
"generators/content_scaffold/templates/controller.rb",
|
42
|
+
"generators/content_scaffold/templates/model.rb",
|
43
|
+
"generators/content_scaffold/templates/view_edit.html.erb",
|
44
|
+
"generators/content_scaffold/templates/view_index.html.erb",
|
45
|
+
"generators/content_scaffold/templates/view_new.html.erb",
|
46
|
+
"generators/content_scaffold/templates/view_show.html.erb",
|
23
47
|
"init.rb",
|
48
|
+
"lib/component.rb",
|
49
|
+
"lib/content/adapters/base.rb",
|
50
|
+
"lib/content/adapters/cabinet_adapter.rb",
|
51
|
+
"lib/content/adapters/tyrant_adapter.rb",
|
52
|
+
"lib/content/item.rb",
|
53
|
+
"lib/content/item_association_class_methods.rb",
|
54
|
+
"lib/content/item_class_methods.rb",
|
55
|
+
"lib/content/item_dirty_methods.rb",
|
56
|
+
"lib/content/item_finder_class_methods.rb",
|
57
|
+
"lib/content/manager.rb",
|
58
|
+
"lib/content/sublayout.rb",
|
59
|
+
"lib/content/template.rb",
|
24
60
|
"lib/content_manager.rb",
|
25
61
|
"test/content_manager_test.rb",
|
26
62
|
"test/test_helper.rb"
|
data/doc/created.rid
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Tue, 30 Jun 2009 21:15:33 +1000
|
@@ -0,0 +1,487 @@
|
|
1
|
+
<?xml version="1.0" encoding="iso-8859-1"?>
|
2
|
+
<!DOCTYPE html
|
3
|
+
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
4
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
5
|
+
|
6
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
7
|
+
<head>
|
8
|
+
<title>File: README.rdoc</title>
|
9
|
+
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
|
10
|
+
<meta http-equiv="Content-Script-Type" content="text/javascript" />
|
11
|
+
<link rel="stylesheet" href=".././rdoc-style.css" type="text/css" media="screen" />
|
12
|
+
<script type="text/javascript">
|
13
|
+
// <![CDATA[
|
14
|
+
|
15
|
+
function popupCode( url ) {
|
16
|
+
window.open(url, "Code", "resizable=yes,scrollbars=yes,toolbar=no,status=no,height=150,width=400")
|
17
|
+
}
|
18
|
+
|
19
|
+
function toggleCode( id ) {
|
20
|
+
if ( document.getElementById )
|
21
|
+
elem = document.getElementById( id );
|
22
|
+
else if ( document.all )
|
23
|
+
elem = eval( "document.all." + id );
|
24
|
+
else
|
25
|
+
return false;
|
26
|
+
|
27
|
+
elemStyle = elem.style;
|
28
|
+
|
29
|
+
if ( elemStyle.display != "block" ) {
|
30
|
+
elemStyle.display = "block"
|
31
|
+
} else {
|
32
|
+
elemStyle.display = "none"
|
33
|
+
}
|
34
|
+
|
35
|
+
return true;
|
36
|
+
}
|
37
|
+
|
38
|
+
// Make codeblocks hidden by default
|
39
|
+
document.writeln( "<style type=\"text/css\">div.method-source-code { display: none }</style>" )
|
40
|
+
|
41
|
+
// ]]>
|
42
|
+
</script>
|
43
|
+
|
44
|
+
</head>
|
45
|
+
<body>
|
46
|
+
|
47
|
+
|
48
|
+
|
49
|
+
<div id="fileHeader">
|
50
|
+
<h1>README.rdoc</h1>
|
51
|
+
<table class="header-table">
|
52
|
+
<tr class="top-aligned-row">
|
53
|
+
<td><strong>Path:</strong></td>
|
54
|
+
<td>README.rdoc
|
55
|
+
</td>
|
56
|
+
</tr>
|
57
|
+
<tr class="top-aligned-row">
|
58
|
+
<td><strong>Last Update:</strong></td>
|
59
|
+
<td>Tue Jun 30 21:15:30 +1000 2009</td>
|
60
|
+
</tr>
|
61
|
+
</table>
|
62
|
+
</div>
|
63
|
+
<!-- banner header -->
|
64
|
+
|
65
|
+
<div id="bodyContent">
|
66
|
+
|
67
|
+
|
68
|
+
|
69
|
+
<div id="contextContent">
|
70
|
+
|
71
|
+
<div id="description">
|
72
|
+
<h1>Content::Manager</h1>
|
73
|
+
<h2>Overview</h2>
|
74
|
+
<p>
|
75
|
+
The Content::Manager plugin provides Content Management capability for
|
76
|
+
Rails applications. Content::Manager is built on top of a Tokyo Tyrant (or
|
77
|
+
Tokyo Cabinet) schema-less table database. Using standard Rails
|
78
|
+
functionality of models, views and controllers with some additional
|
79
|
+
"glue", Content::Manager makes it straight-forward to build
|
80
|
+
component-based, content-managed Rails applications.
|
81
|
+
</p>
|
82
|
+
<h2>How it works</h2>
|
83
|
+
<p>
|
84
|
+
The Content Manager is invoked by the final route in the routes file:
|
85
|
+
</p>
|
86
|
+
<pre>
|
87
|
+
map.content_item '*content_item_url', :controller => 'content', :action => 'show'
|
88
|
+
</pre>
|
89
|
+
<p>
|
90
|
+
This route tells Rails to pass any URL (in the content_item_url parameter)
|
91
|
+
to the show action of the ContentController.
|
92
|
+
</p>
|
93
|
+
<p>
|
94
|
+
When the show action in the ContentController is invoked (inherited from
|
95
|
+
Content::Manager), the before_filter :current_content_item in the
|
96
|
+
ApplicationController is invoked first. This causes the Content::Item
|
97
|
+
specified by the URL in content_item_url to be loaded using
|
98
|
+
Content::Item.find_by_url params[:content_item_url].
|
99
|
+
</p>
|
100
|
+
<p>
|
101
|
+
Back in the ContentController#show action, the current_content_item is
|
102
|
+
checked if it is nil, if it is missing a template or if its template is
|
103
|
+
missing a sublayout. In any of these cases, a 404 Not Found error is
|
104
|
+
rendered (specified in app/views/errors/error404.html.erb).
|
105
|
+
</p>
|
106
|
+
<p>
|
107
|
+
Assuming we have a valid Content::Item with a template and a sublayout,
|
108
|
+
then the containers are rendered (using prerender_containers, content_for,
|
109
|
+
render_container and render_component in Content::Manager). The contents of
|
110
|
+
each container are stored for later use by yield within the Sublayout.
|
111
|
+
Finally, the Sublayout is rendered as a template using render :template. At
|
112
|
+
this point, no layout is specified, so Sublayouts have to provide the full
|
113
|
+
HTML. This may change in the future though.
|
114
|
+
</p>
|
115
|
+
<p>
|
116
|
+
The Content::Manager#render_component method requires special mention. This
|
117
|
+
method operates by creating a new Rack request and handing the request off
|
118
|
+
to the ActionController::Routing::Routes table. Any error from here is
|
119
|
+
raised as a RuntimeError with the message being the full HTML returned from
|
120
|
+
the error. In the case of Content::Manager, any errors from the components
|
121
|
+
is returned as the page error.
|
122
|
+
</p>
|
123
|
+
<h2>Creating a Content Item</h2>
|
124
|
+
<ol>
|
125
|
+
<li>Create the content_scaffold.
|
126
|
+
|
127
|
+
<pre>
|
128
|
+
$ script/generate content_scaffold Video duration:string
|
129
|
+
exists app/models/content
|
130
|
+
exists app/controllers/content
|
131
|
+
create app/views/content/videos
|
132
|
+
exists app/views/errors
|
133
|
+
exists app/views/sublayouts
|
134
|
+
exists public/stylesheets/content
|
135
|
+
create app/views/content/videos/index.html.erb
|
136
|
+
create app/views/content/videos/show.html.erb
|
137
|
+
create app/views/content/videos/new.html.erb
|
138
|
+
create app/views/content/videos/edit.html.erb
|
139
|
+
create app/controllers/content/videos_controller.rb
|
140
|
+
route map.resources :videos
|
141
|
+
create app/models/content/video.rb
|
142
|
+
</pre>
|
143
|
+
</li>
|
144
|
+
<li>Edit the model (app/models/content/video.rb) and add fields
|
145
|
+
|
146
|
+
<p>
|
147
|
+
You can use the field method or fields method. You can optionally specify
|
148
|
+
the type of the field as the second parameter to field. The following
|
149
|
+
methods are available: field_name, fieldname=, fieldname_changed?,
|
150
|
+
fieldname_change, fieldname_was.
|
151
|
+
</p>
|
152
|
+
<pre>
|
153
|
+
class Content::Video < Content::Item
|
154
|
+
field :duration
|
155
|
+
end
|
156
|
+
</pre>
|
157
|
+
</li>
|
158
|
+
<li>Add associations (has_many, has_one, belongs_to)
|
159
|
+
|
160
|
+
<dl>
|
161
|
+
<dt>has_many</dt><dd>creates a one-to-many association implemented as an array of ID’s. The
|
162
|
+
following methods are available: singular_association_ids,
|
163
|
+
singular_association_ids=, association, association=.
|
164
|
+
|
165
|
+
</dd>
|
166
|
+
<dt>has_one</dt><dd>creates a one-to-one association implemented as the ID of the other item.
|
167
|
+
The following methods are available: association_id, association_id=,
|
168
|
+
association, association=.
|
169
|
+
|
170
|
+
</dd>
|
171
|
+
<dt>belongs_to</dt><dd>creates a many-to-one association implemented as the ID of the other item.
|
172
|
+
The following methods are available: association_id, association_id=,
|
173
|
+
association, association=.
|
174
|
+
|
175
|
+
</dd>
|
176
|
+
</dl>
|
177
|
+
<pre>
|
178
|
+
class Content::Video < Content::Item
|
179
|
+
field :heading
|
180
|
+
field :duration
|
181
|
+
belongs_to :parent_section
|
182
|
+
end
|
183
|
+
</pre>
|
184
|
+
</li>
|
185
|
+
<li>Add validations. All of the standard ActiveRecord validations are
|
186
|
+
available.
|
187
|
+
|
188
|
+
<pre>
|
189
|
+
class Content::Video < Content::Item
|
190
|
+
field :heading
|
191
|
+
field :duration
|
192
|
+
belongs_to :parent_section
|
193
|
+
validates_presence_of :heading
|
194
|
+
end
|
195
|
+
</pre>
|
196
|
+
</li>
|
197
|
+
<li>Set any indexes required.
|
198
|
+
|
199
|
+
<pre>
|
200
|
+
class Content::Video < Content::Item
|
201
|
+
field :heading
|
202
|
+
field :duration
|
203
|
+
belongs_to :parent_section
|
204
|
+
validates_presence_of :heading
|
205
|
+
index :heading, :lexical
|
206
|
+
end
|
207
|
+
</pre>
|
208
|
+
</li>
|
209
|
+
<li>Edit the routes file to fix the default route created. In other words:
|
210
|
+
|
211
|
+
<pre>
|
212
|
+
ActionController::Routing::Routes.draw do |map|
|
213
|
+
map.resources :videos # << note it is misplaced
|
214
|
+
|
215
|
+
# Content =======================================================
|
216
|
+
map.namespace :content do |content|
|
217
|
+
# Core CMS
|
218
|
+
content.resources :sublayouts, :only => [:index, :show]
|
219
|
+
content.resources :components, :only => [:index, :show]
|
220
|
+
content.resources :templates
|
221
|
+
|
222
|
+
# Extensions
|
223
|
+
content.resources :articles
|
224
|
+
content.resources :photo_galleries
|
225
|
+
content.resources :photos
|
226
|
+
end
|
227
|
+
# END Content
|
228
|
+
|
229
|
+
...
|
230
|
+
</pre>
|
231
|
+
<p>
|
232
|
+
becomes:
|
233
|
+
</p>
|
234
|
+
<pre>
|
235
|
+
ActionController::Routing::Routes.draw do |map|
|
236
|
+
|
237
|
+
# Content =======================================================
|
238
|
+
map.namespace :content do |content|
|
239
|
+
# Core CMS
|
240
|
+
content.resources :sublayouts, :only => [:index, :show]
|
241
|
+
content.resources :components, :only => [:index, :show]
|
242
|
+
content.resources :templates
|
243
|
+
|
244
|
+
# Extensions
|
245
|
+
content.resources :videos # << it should go here
|
246
|
+
content.resources :articles
|
247
|
+
content.resources :photo_galleries
|
248
|
+
content.resources :photos
|
249
|
+
end
|
250
|
+
# END Content
|
251
|
+
|
252
|
+
...
|
253
|
+
</pre>
|
254
|
+
</li>
|
255
|
+
<li>Edit your controller and views as for any normal Rails app. The controller
|
256
|
+
should typically redirect to the index action in the create and update
|
257
|
+
actions instead of to the show action. In other words:
|
258
|
+
|
259
|
+
<pre>
|
260
|
+
format.html { redirect_to(@video) }
|
261
|
+
</pre>
|
262
|
+
<p>
|
263
|
+
becomes
|
264
|
+
</p>
|
265
|
+
<pre>
|
266
|
+
format.html { redirect_to(content_videos_url) }
|
267
|
+
</pre>
|
268
|
+
</li>
|
269
|
+
</ol>
|
270
|
+
<h2>Using Content Items</h2>
|
271
|
+
<p>
|
272
|
+
Once you have created your Content::Item, you can use most of the standard
|
273
|
+
ActiveRecord finder methods:
|
274
|
+
</p>
|
275
|
+
<pre>
|
276
|
+
section = Content::Section.new()
|
277
|
+
section.heading = "My Heading"
|
278
|
+
section.body = "My Body"
|
279
|
+
section.new_record? # => true
|
280
|
+
section.save
|
281
|
+
section.new_record? # => false
|
282
|
+
|
283
|
+
section = Content::Section.new(:heading => "My Heading", :body => "My Body")
|
284
|
+
section.save
|
285
|
+
|
286
|
+
section = Content::Section.new() do |sect|
|
287
|
+
sect.heading = "My Heading"
|
288
|
+
sect.body = "My Body"
|
289
|
+
end
|
290
|
+
section.save
|
291
|
+
|
292
|
+
Content::Section.find_by_id(id)
|
293
|
+
Content::Section.find id
|
294
|
+
|
295
|
+
Content::Section.first
|
296
|
+
Content::Section.find(:first)
|
297
|
+
Content::Section.find(:first, :conditions => { :user_name => user_name })
|
298
|
+
Content::Section.find(:first, :order => :created_on, :offset => 5)
|
299
|
+
|
300
|
+
Content::Section.last
|
301
|
+
Content::Section.find(:last)
|
302
|
+
Content::Section.find(:last, :conditions => { :user_name => user_name })
|
303
|
+
Content::Section.find(:last, :order => :created_on, :offset => 5)
|
304
|
+
|
305
|
+
Content::Section.all
|
306
|
+
Content::Section.find(:all)
|
307
|
+
Content::Section.find(:all, :conditions => { :friends => "Bob" })
|
308
|
+
Content::Section.find(:all, :offset => 10, :limit => 10)
|
309
|
+
|
310
|
+
Content::Section.find_by_url "/url"
|
311
|
+
Content::Section.find_by_url_and_status "/url", "active"
|
312
|
+
</pre>
|
313
|
+
<h2>Creating a Sublayout</h2>
|
314
|
+
<p>
|
315
|
+
A sublayout is a standard Rails layout file, except it is created in the
|
316
|
+
apps/views/sublayouts folder instead of apps/views/layouts. The sublayout
|
317
|
+
accesses the contents of the containers as specified in the Template by
|
318
|
+
yielding the container name. For example, to insert the contents of the
|
319
|
+
"left" container, simply yield :left. Note that yield without an
|
320
|
+
argument is the same as yield :contents as it accesses the
|
321
|
+
"contents" container.
|
322
|
+
</p>
|
323
|
+
<h2>Creating a Template</h2>
|
324
|
+
<p>
|
325
|
+
A Template joins a Sublayout together with the contents if the
|
326
|
+
Sublayout’s containers. This allows the contents of the containers to be
|
327
|
+
dynamically changed without changing the code of the Sublayout. A Template
|
328
|
+
is just a Content::Item which has two interesting fields: heading (the name
|
329
|
+
of the Template), sublayout (the relative path of the sublayout within the
|
330
|
+
app/views/sublayouts folder). In addition to these standard fields, fields
|
331
|
+
named after the containers in the sublayout are also created. For example,
|
332
|
+
if the sublayout has the following containers [:left, :contents, :right],
|
333
|
+
then there will be "left", "contents" and
|
334
|
+
"right" fields in the Template. The value of each field is an
|
335
|
+
array of the components for that container.
|
336
|
+
</p>
|
337
|
+
<h2>Creating a Component</h2>
|
338
|
+
<ol>
|
339
|
+
<li>Create the component_scaffold.
|
340
|
+
|
341
|
+
<pre>
|
342
|
+
$ script/generate component_scaffold VideoPlayer
|
343
|
+
exists app/models/components
|
344
|
+
exists app/controllers/components
|
345
|
+
create app/views/components/video_players
|
346
|
+
exists public/stylesheets/components
|
347
|
+
create app/views/components/video_players/index.html.erb
|
348
|
+
create app/views/components/video_players/show.html.erb
|
349
|
+
create app/views/components/video_players/new.html.erb
|
350
|
+
create app/views/components/video_players/edit.html.erb
|
351
|
+
create public/stylesheets/components/video_player.css
|
352
|
+
create app/controllers/components/video_players_controller.rb
|
353
|
+
route map.resources :video_players
|
354
|
+
create app/models/components/video_player.rb
|
355
|
+
</pre>
|
356
|
+
</li>
|
357
|
+
<li>Edit the model (app/models/components/video_player.rb).
|
358
|
+
|
359
|
+
<pre>
|
360
|
+
class Components::VideoPlayer < Content::Item
|
361
|
+
end
|
362
|
+
</pre>
|
363
|
+
</li>
|
364
|
+
<li>As you can see from above, the component is simply another Content::Item,
|
365
|
+
so all of the capabilities covered in creating a Content::Item apply.
|
366
|
+
|
367
|
+
</li>
|
368
|
+
<li>Edit the routes file to fix the default route created. In other words:
|
369
|
+
|
370
|
+
<pre>
|
371
|
+
ActionController::Routing::Routes.draw do |map|
|
372
|
+
map.resources :video_players # << note it is misplaced
|
373
|
+
|
374
|
+
# Components ===================================================
|
375
|
+
map.namespace :components do |component|
|
376
|
+
# Core CMS
|
377
|
+
component.resources :containers, :only => [:show]
|
378
|
+
|
379
|
+
# Extensions
|
380
|
+
component.resources :section_heros
|
381
|
+
end
|
382
|
+
# END Components
|
383
|
+
...
|
384
|
+
</pre>
|
385
|
+
<p>
|
386
|
+
becomes:
|
387
|
+
</p>
|
388
|
+
<pre>
|
389
|
+
ActionController::Routing::Routes.draw do |map|
|
390
|
+
# Components ===================================================
|
391
|
+
map.namespace :components do |component|
|
392
|
+
# Core CMS
|
393
|
+
component.resources :containers, :only => [:show]
|
394
|
+
|
395
|
+
# Extensions
|
396
|
+
component.resources :section_heros
|
397
|
+
component.resources :video_players # << it goes here
|
398
|
+
# END Components
|
399
|
+
...
|
400
|
+
</pre>
|
401
|
+
</li>
|
402
|
+
<li>Edit your controller and views as for any normal Rails app. The controller
|
403
|
+
should typically redirect to the index action in the create and update
|
404
|
+
actions instead of to the show action. In other words:
|
405
|
+
|
406
|
+
<pre>
|
407
|
+
format.html { redirect_to(@video_player) }
|
408
|
+
</pre>
|
409
|
+
<p>
|
410
|
+
becomes
|
411
|
+
</p>
|
412
|
+
<pre>
|
413
|
+
format.html { redirect_to(components_video_player_url) }
|
414
|
+
</pre>
|
415
|
+
</li>
|
416
|
+
<li>The show view is the view that will be embedded within pages. The standard
|
417
|
+
component layout (app/layouts/components.html.erb) will wrap the component
|
418
|
+
in a <div> with the "component" class and a class with the
|
419
|
+
name of the component model (in this case "video_player"), as
|
420
|
+
follows:
|
421
|
+
|
422
|
+
<pre>
|
423
|
+
<div id="video_player_6253" class="video_player component">
|
424
|
+
<!-- component body goes here -->
|
425
|
+
</div>
|
426
|
+
</pre>
|
427
|
+
</li>
|
428
|
+
</ol>
|
429
|
+
<p>
|
430
|
+
This allows for easy styling of the specific component and for all
|
431
|
+
components.
|
432
|
+
</p>
|
433
|
+
<ol>
|
434
|
+
<li>Edit the component stylesheet
|
435
|
+
public/stylesheets/components/video_player.css. Put any styles specific to
|
436
|
+
this component in this stylesheet. Don‘t worry if you think this step
|
437
|
+
will create a problem at runtime, as all of these stylesheets are combined
|
438
|
+
at production time into a single stylesheet.
|
439
|
+
|
440
|
+
<pre>
|
441
|
+
.video_player {}
|
442
|
+
</pre>
|
443
|
+
</li>
|
444
|
+
</ol>
|
445
|
+
<h2>Copyright</h2>
|
446
|
+
<p>
|
447
|
+
Copyright (c) 2009 Seth Yates
|
448
|
+
</p>
|
449
|
+
<p>
|
450
|
+
Copyright (c) 2009 Independent Digital Media Pty Ltd
|
451
|
+
</p>
|
452
|
+
<p>
|
453
|
+
See LICENSE for details.
|
454
|
+
</p>
|
455
|
+
|
456
|
+
</div>
|
457
|
+
|
458
|
+
|
459
|
+
</div>
|
460
|
+
|
461
|
+
|
462
|
+
</div>
|
463
|
+
|
464
|
+
|
465
|
+
<!-- if includes -->
|
466
|
+
|
467
|
+
<div id="section">
|
468
|
+
|
469
|
+
|
470
|
+
|
471
|
+
|
472
|
+
|
473
|
+
|
474
|
+
|
475
|
+
|
476
|
+
<!-- if method_list -->
|
477
|
+
|
478
|
+
|
479
|
+
</div>
|
480
|
+
|
481
|
+
|
482
|
+
<div id="validator-badges">
|
483
|
+
<p><small><a href="http://validator.w3.org/check/referer">[Validate]</a></small></p>
|
484
|
+
</div>
|
485
|
+
|
486
|
+
</body>
|
487
|
+
</html>
|