rooftop-rails-extras 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +24 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +621 -0
- data/README.md +162 -0
- data/Rakefile +2 -0
- data/app/controllers/concerns/rooftop/rails/extras/contact_form_handler.rb +46 -0
- data/app/helpers/rooftop/rails/extras/navigation_helper.rb +117 -0
- data/app/helpers/rooftop/rails/extras/related_fields_helper.rb +14 -0
- data/app/models/concerns/rooftop/rails/extras/contact_form.rb +51 -0
- data/app/models/concerns/rooftop/rails/extras/page_redirect.rb +20 -0
- data/app/models/concerns/rooftop/rails/extras/resolved_children.rb +21 -0
- data/app/models/concerns/rooftop/rails/extras/resource_search.rb +48 -0
- data/lib/generators/rooftop/extras_generator.rb +21 -0
- data/lib/generators/rooftop/page_generator.rb +14 -0
- data/lib/generators/rooftop/pages_controller_generator.rb +27 -0
- data/lib/generators/rooftop/post_generator.rb +9 -0
- data/lib/generators/rooftop/template_generator.rb +9 -0
- data/lib/generators/rooftop/templates/application.html.erb +14 -0
- data/lib/generators/rooftop/templates/index.html.erb +2 -0
- data/lib/generators/rooftop/templates/page_class.rb.erb +4 -0
- data/lib/generators/rooftop/templates/pages_controller.rb.erb +49 -0
- data/lib/generators/rooftop/templates/post_class.rb.erb +3 -0
- data/lib/generators/rooftop/templates/show.html.erb +3 -0
- data/lib/generators/rooftop/templates/template.html.erb +8 -0
- data/lib/rooftop/rails/extras/engine.rb +38 -0
- data/lib/rooftop/rails/extras/version.rb +7 -0
- data/lib/rooftop/rails/extras.rb +14 -0
- data/rooftop-rails-extras.gemspec +29 -0
- metadata +142 -0
data/README.md
ADDED
@@ -0,0 +1,162 @@
|
|
1
|
+
# Rooftop::Rails::Extras
|
2
|
+
A selection of things we're finding handy at [Error](http://error.agency) so you might, too.
|
3
|
+
|
4
|
+
## `Rooftop::Rails::Extras::ResourceSearch` - Free-text Searchable resources
|
5
|
+
Rooftop CMS search is a work in progress, and only supports basic WordPress LIKE queries at the moment. You can do a little better than that, at the expense of getting a collection of all resources, if you do a regex match in Rails.
|
6
|
+
|
7
|
+
```
|
8
|
+
class SomePostType
|
9
|
+
include Rooftop::Post
|
10
|
+
include Rooftop::Rails::Extras::ResourceSearch #searches title and content by default
|
11
|
+
|
12
|
+
# add extra fields to search, by creating a lambda which returns text. Markup and punctuation is stripped after calling it.
|
13
|
+
add_search_field ->(record) {
|
14
|
+
record.fields.some_custom_field
|
15
|
+
}
|
16
|
+
|
17
|
+
# Or a more complicated example, where a custom field is returning an object of some sort
|
18
|
+
add_search_field ->(record) {
|
19
|
+
record.fields.some_complex_object[:some][:deep][:data]
|
20
|
+
}
|
21
|
+
|
22
|
+
# or even a collection, from an ACF repeater
|
23
|
+
add_search_field ->(record) {
|
24
|
+
content = record.fields.some_repeater.collect do |object|
|
25
|
+
object.some.deep.thing
|
26
|
+
end
|
27
|
+
content.join(" ") #return the collection, joined with a space
|
28
|
+
}
|
29
|
+
|
30
|
+
end
|
31
|
+
```
|
32
|
+
|
33
|
+
## Contact form handling
|
34
|
+
Almost every website we build needs a simple mail handler. Extending the [mail_form gem](https://github.com/plataformatec/mail_form), we've added a way to handle these with minimal fuss. Some assumptions are made in the name of expedience; if you need more than this, you probably need to use `mail_form` directly.
|
35
|
+
|
36
|
+
### Create a contact form model, inheriting from the Rooftop extras one
|
37
|
+
First define a model which inherits from `Rooftop::Rails::Extras::ContactForm`. You need to do some setup in here, including:
|
38
|
+
|
39
|
+
* Defining the fields (and types) you need to handle
|
40
|
+
* The to / from addresses, and any extra headers you want to define in the email
|
41
|
+
|
42
|
+
```
|
43
|
+
class ContactForm < Rooftop::Rails::Extras::ContactForm #you can call your class anything
|
44
|
+
#the values of these hashes are Rails form helper method names
|
45
|
+
self.fields = {
|
46
|
+
name: :text_field,
|
47
|
+
email: :text_field,
|
48
|
+
message: :text_area
|
49
|
+
}
|
50
|
+
self.to = "the email you want to send the contact messages to"
|
51
|
+
self.from = "the email from which you want to send the messages"
|
52
|
+
end
|
53
|
+
```
|
54
|
+
|
55
|
+
### Create a controller and add the mixin
|
56
|
+
Write a simple controller and add the `Rooftop::Rails::Extras::ContactFormHandler` mixin.
|
57
|
+
|
58
|
+
```
|
59
|
+
class ContactFormsController < ApplicationController # you can call your controller anything to suit
|
60
|
+
include Rooftop::Rails::Extras::ContactFormHandler
|
61
|
+
# Declare the class you've just defined, above. If you called it something else, change it here.
|
62
|
+
self.contact_form = ::ContactForm
|
63
|
+
end
|
64
|
+
```
|
65
|
+
|
66
|
+
### Create a route
|
67
|
+
If you called your controller something else, you need to tweak the route accordingly. Of course, this is all just normal Rails routes so you can do what you need to, as long as a POST request hits your controller.
|
68
|
+
|
69
|
+
```
|
70
|
+
resources :contact_forms, only: :create
|
71
|
+
```
|
72
|
+
|
73
|
+
### Define a 'thankyou' page in Rooftop CMS
|
74
|
+
When the form is submitted, the controller redirects the user to a thankyou page, which is any page you want. You need to derive the ID of this page somehow, to get it into a hidden field in the form. Generally, we create a field in Rooftop for this, and decorate the Page model with a'thankyou_page_id' method which returns the ID.
|
75
|
+
|
76
|
+
### Create your form
|
77
|
+
How you build the form is up to you, but here's a quick way to use the fields you defined above.
|
78
|
+
|
79
|
+
```
|
80
|
+
<%= form_for ContactForm.new, method: :post, class: 'contact' do |f| %>
|
81
|
+
<!-- The form will create a flash with the errors from the form, if any -->
|
82
|
+
<% if flash[:notice].present? %>
|
83
|
+
<p class="form-errors">
|
84
|
+
Your message couldn't be sent: <%= flash[:notice] %>
|
85
|
+
</p>
|
86
|
+
<% end %>
|
87
|
+
<!-- f.object is the ContactForm instance above, so we know it responds to `fields` -->
|
88
|
+
<% f.object.fields.each do |field, type| %>
|
89
|
+
<!-- iterate over each field, setting the value if passed in, and adding an error class if there's an error on the field -->
|
90
|
+
<p>
|
91
|
+
<%= f.label field %>
|
92
|
+
<%= f.send(
|
93
|
+
type,
|
94
|
+
field,
|
95
|
+
class: (params[:errors].present? && params[:errors].include?(field.to_s)) ? "has-errors" : "",
|
96
|
+
value: params[field],
|
97
|
+
required: true
|
98
|
+
)%>
|
99
|
+
</p>
|
100
|
+
<% end %>
|
101
|
+
<!-- these hidden tags tell the controller how to redirect the user. If there's an error, we'll redirect back to this page, with get params for the errors and each completed field. If there isn't, the user is redirected to the thankyou page -->
|
102
|
+
<%= hidden_field_tag :from_page, page.id %>
|
103
|
+
<%= hidden_field_tag :to_page, page.thankyou_page_id %>
|
104
|
+
|
105
|
+
<p><input type="submit" id="submit" value="Send" /></p>
|
106
|
+
<% end %>
|
107
|
+
```
|
108
|
+
|
109
|
+
### Handling Errors
|
110
|
+
`Rooftop::Rails::Extras::ContactForm` assumes every field you enter is required. If there are errors on the model, the controller will return the user to the original page (as a redirect) but add some querystring params:
|
111
|
+
|
112
|
+
* `errors[]` - an array of each fieldname with errors (in our example above, we check for this and add a classname on the offending inputs
|
113
|
+
* params for each entered field, so you can set the field values to save data re-entry
|
114
|
+
|
115
|
+
## Navigation View Helpers
|
116
|
+
For nested post types (pages, products etc.) you might well want to create subnavigation, showing the path to this page, its siblings and parent pages. We use this often enough at [Error Agency](http://error.agency) to have a helper do the iteration.
|
117
|
+
|
118
|
+
We also generally use WordPress menus in Rooftop for the main navigation on a site, and also footer links.
|
119
|
+
|
120
|
+
### `subnavigation_for()` helper
|
121
|
+
Given a page, this helper will return a nested unordered list of links, starting at the root of the page, and rendering the page, it's parents and their siblings, and its ancestors above parents.
|
122
|
+
|
123
|
+
Here's a call to the helper in a view, with the default options shown:
|
124
|
+
|
125
|
+
```
|
126
|
+
<%# @page is defined in your controller as a call to Page.find() %>
|
127
|
+
<%= subnavigation_for(@page, class: 'subnavigation-list', current_class: 'is-current', current: nil) %>
|
128
|
+
```
|
129
|
+
|
130
|
+
If you want to specify different class names for the UL and currently-active page, just amend the options.
|
131
|
+
|
132
|
+
If you want to highlight *another page* when the subnav is rendered, that's fine too:
|
133
|
+
|
134
|
+
```
|
135
|
+
<%= subnavigation_for(@page, current: @another_page) %>
|
136
|
+
```
|
137
|
+
|
138
|
+
You might want to do this if, for example, your markup requires some navigational components in 2 different places.
|
139
|
+
|
140
|
+
### `menu_for()` helper
|
141
|
+
Given a `Rooftop::Menus::Menu` object, `menu_for()` will return a nested unordered list.
|
142
|
+
|
143
|
+
```
|
144
|
+
<%# @menu is defined in your controller as a call to Rooftop::Menus::Menu.find()
|
145
|
+
<%= menu_for(@menu, current_class: 'is-current') %>
|
146
|
+
```
|
147
|
+
|
148
|
+
A utility `menu-level-[x]` class is added to each level of the nested menu, in case you want to target a particular level in a different way with css. Useful for dropdown menus.
|
149
|
+
|
150
|
+
### `breadcrumbs_for()` helper
|
151
|
+
Breadcrumbs are similar to a nested subnavigation, but they only represent a direct path to this page (or other nested post type).
|
152
|
+
|
153
|
+
```
|
154
|
+
<%# @page is defined in your controller as a call to Page.find() %>
|
155
|
+
<%= breadcrumbs_for(@page, class: 'breadcrumbs', current_class: 'is-current') %>
|
156
|
+
```
|
157
|
+
|
158
|
+
# Licence
|
159
|
+
This gem is licenced GPLv3; contributions are most welcome!
|
160
|
+
|
161
|
+
# Contributing
|
162
|
+
The usual: fork, change, PR. Clean PRs (rebased if necessary) are preferred, but anything you can do is valuable.
|
data/Rakefile
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
module Rooftop
|
2
|
+
module Rails
|
3
|
+
module Extras
|
4
|
+
# A controller mixin to handle contact forms easily. If you don't use Page as your page class, you need to specify it.
|
5
|
+
# You also need to specify the contact form class you're using (which needs to inherit from Rooftop::Rails::Extras::ContactForm)
|
6
|
+
module ContactFormHandler
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
class_methods do
|
10
|
+
def contact_form=(klass)
|
11
|
+
@contact_form = klass
|
12
|
+
end
|
13
|
+
|
14
|
+
def contact_form
|
15
|
+
@contact_form
|
16
|
+
end
|
17
|
+
|
18
|
+
def page_class=(klass)
|
19
|
+
@page_class = klass
|
20
|
+
end
|
21
|
+
|
22
|
+
def page_class
|
23
|
+
@page_class || ::Page
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def create
|
28
|
+
raise ArgumentError, "You need to call contact_form=(YourClass) in your controller, which must inherit from Rooftop::Rails::Extras::ContactForm" unless self.class.contact_form.ancestors.include?(Rooftop::Rails::Extras::ContactForm)
|
29
|
+
raise ArgumentError, "You need to include hidden fields for from_page and to_page IDs, so this controller can redirect appropriately" unless params.has_key?(:from_page) && params.has_key?(:to_page)
|
30
|
+
form = self.class.contact_form.new(contact_form_params)
|
31
|
+
from_page, to_page = *self.class.page_class.where(post__in: [params[:from_page], params[:to_page]], orderby: :post__in)
|
32
|
+
if form.deliver
|
33
|
+
redirect_to Rooftop::Rails::RouteResolver.new(:page, to_page.nested_path).resolve
|
34
|
+
else
|
35
|
+
redirect_to Rooftop::Rails::RouteResolver.new(:page, from_page.nested_path).resolve(contact_form_params.merge(errors: form.errors.keys)), notice: form.errors.full_messages.to_sentence
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
def contact_form_params
|
41
|
+
params.require(self.class.contact_form.to_s.underscore.to_sym).permit(self.class.contact_form.fields.keys)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
module Rooftop
|
2
|
+
module Rails
|
3
|
+
module Extras
|
4
|
+
module NavigationHelper
|
5
|
+
def subnavigation_for(entity, opts = {})
|
6
|
+
default_opts = {
|
7
|
+
class: 'subnavigation-list',
|
8
|
+
current_class: 'is-current',
|
9
|
+
current: nil
|
10
|
+
}.merge(opts)
|
11
|
+
raise ArgumentError, "#{entity.class} isn't a nested class" unless entity.respond_to?(:resolved_children)
|
12
|
+
raise ArgumentError, "You passed a current #{entity.class.to_s.downcase} which isn't a #{entity.class} object" unless (default_opts[:current].nil? || default_opts[:current].is_a?(entity.class))
|
13
|
+
if entity.resolved_children.any?
|
14
|
+
# byebug
|
15
|
+
content_tag(:ul, class: default_opts[:class]) do
|
16
|
+
items = entity.resolved_children.collect do |child|
|
17
|
+
subnavigation_item_for(child, default_opts)
|
18
|
+
end
|
19
|
+
items.join.html_safe
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def menu_for(menu, opts={})
|
25
|
+
default_opts = {
|
26
|
+
class: 'main-navigation-list',
|
27
|
+
current_class: 'is-current'
|
28
|
+
}.merge(opts)
|
29
|
+
content_tag(:ul, class: default_opts[:class]) do
|
30
|
+
items = menu.items.collect do |item|
|
31
|
+
menu_item_for(item, opts)
|
32
|
+
end
|
33
|
+
items.join.html_safe
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def menu_item_for(item, opts={})
|
38
|
+
default_opts = {
|
39
|
+
current_class: 'is-current',
|
40
|
+
level: 1
|
41
|
+
}.merge(opts)
|
42
|
+
item_path = path_for_menu_item(item)
|
43
|
+
item_class = path_matches?(item_path) ? default_opts[:current_class] : ""
|
44
|
+
content_tag :li, class: item_class do
|
45
|
+
link = content_tag :a, href: item_path do
|
46
|
+
item.title.html_safe
|
47
|
+
end
|
48
|
+
child_links = ""
|
49
|
+
if item.children.present?
|
50
|
+
child_links = content_tag :ul, class: "menu-level-#{default_opts[:level]}" do
|
51
|
+
items = item.children.collect do |child|
|
52
|
+
menu_item_for(child, default_opts.merge({level: default_opts[:level] + 1}))
|
53
|
+
end
|
54
|
+
items.join.html_safe
|
55
|
+
end
|
56
|
+
end
|
57
|
+
link + child_links
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def breadcrumbs_for(entity, opts = {})
|
62
|
+
default_opts = {
|
63
|
+
class: 'breadcrumbs',
|
64
|
+
current_class: 'is-current'
|
65
|
+
}.merge(opts)
|
66
|
+
content_tag :ul, class: default_opts[:class] do
|
67
|
+
items = entity.ancestors.reverse.inject({}) do |links,ancestor|
|
68
|
+
links[(links.present? ? "#{links.keys.last}/#{ancestor.slug}" : ancestor.slug)] = ancestor
|
69
|
+
links
|
70
|
+
end
|
71
|
+
items.merge!({"#{items.keys.last}/#{entity.slug}" => entity})
|
72
|
+
links = items.collect do |path, item|
|
73
|
+
item_class = (item.id == entity.id ? default_opts[:current_class] : "")
|
74
|
+
content_tag :li, class: item_class do
|
75
|
+
content_tag :a, href: "/#{path}" do
|
76
|
+
item.title
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
links.join.html_safe
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
def subnavigation_item_for(entity, opts = {})
|
86
|
+
list_opts = {}
|
87
|
+
opts.reverse_merge!({level: 1})
|
88
|
+
list_opts[:class] = opts[:current_class] if opts[:current].present? && opts[:current].id == entity.id
|
89
|
+
# The link to the entity
|
90
|
+
content_tag :li, list_opts do
|
91
|
+
|
92
|
+
link = content_tag :a, href: "/#{entity.nested_path}" do
|
93
|
+
entity.title.html_safe
|
94
|
+
end
|
95
|
+
|
96
|
+
child_links = ""
|
97
|
+
|
98
|
+
# Nested ul for children if necessary
|
99
|
+
if opts[:current].present? && (opts[:current].ancestors.collect(&:id).include?(entity.id) || opts[:current].id == entity.id)
|
100
|
+
children = entity.class.where(post_parent: entity.id)
|
101
|
+
if children.any?
|
102
|
+
child_links = content_tag :ul, class: "subnavigation-level-#{opts[:level]}" do
|
103
|
+
items = children.collect do |child|
|
104
|
+
subnavigation_item_for(child, opts.merge({level: opts[:level] + 1}))
|
105
|
+
end
|
106
|
+
items.join.html_safe
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
link + child_links
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Rooftop
|
2
|
+
module Rails
|
3
|
+
module Extras
|
4
|
+
module RelatedFieldsHelper
|
5
|
+
def field_from_related_resource(resource, field)
|
6
|
+
field_data = resource[:advanced].select {|f| f[:fields].collect {|f| f[:name] == field.to_s}.any?}
|
7
|
+
if field_data.any?
|
8
|
+
field_data.first[:fields].find {|f| f[:name] == field.to_s}.try(:[],:value)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Rooftop
|
2
|
+
module Rails
|
3
|
+
module Extras
|
4
|
+
class ContactForm < MailForm::Base
|
5
|
+
class << self
|
6
|
+
attr_accessor :fields, :subject, :to, :from, :headers
|
7
|
+
end
|
8
|
+
|
9
|
+
DEFAULT_FIELDS = {
|
10
|
+
name: :text_field,
|
11
|
+
email: :text_field,
|
12
|
+
message: :text_area
|
13
|
+
}
|
14
|
+
|
15
|
+
def self.inherited(base)
|
16
|
+
(base.fields || DEFAULT_FIELDS).keys.each do |field|
|
17
|
+
base.send(:attribute,field, {validate: true})
|
18
|
+
end
|
19
|
+
|
20
|
+
base.subject ||= "Contact form message"
|
21
|
+
base.to ||= "change.me@#{base.to_s.underscore}"
|
22
|
+
base.from ||= "change.me@#{base.to_s.underscore}"
|
23
|
+
base.headers ||= {}
|
24
|
+
end
|
25
|
+
#
|
26
|
+
# attribute :salutation
|
27
|
+
# attribute :first_name, validate: true
|
28
|
+
# attribute :last_name, validate: true
|
29
|
+
# attribute :email, validate: true
|
30
|
+
# attribute :phone
|
31
|
+
# attribute :message, validate: true
|
32
|
+
# attribute :nickname, captcha: true
|
33
|
+
|
34
|
+
# Declare the e-mail headers. It accepts anything the mail method
|
35
|
+
# in ActionMailer accepts.
|
36
|
+
def headers
|
37
|
+
{
|
38
|
+
subject: self.class.subject,
|
39
|
+
to: self.class.to,
|
40
|
+
from: %("Contact form" <#{self.class.from}>)
|
41
|
+
}.merge(self.class.headers)
|
42
|
+
end
|
43
|
+
|
44
|
+
def fields
|
45
|
+
self.class.fields
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Rooftop
|
2
|
+
module Rails
|
3
|
+
module Extras
|
4
|
+
module PageRedirect
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
def redirect?
|
8
|
+
fields.respond_to?(:redirect_this_page) && fields.redirect_this_page == true && fields.respond_to?(:redirect_url) && fields.redirect_url.present?
|
9
|
+
end
|
10
|
+
|
11
|
+
def redirect_to
|
12
|
+
if redirect?
|
13
|
+
fields.redirect_url
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Rooftop
|
2
|
+
module Rails
|
3
|
+
module Extras
|
4
|
+
module ResolvedChildren
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
def resolved_children
|
8
|
+
child_ids = children.collect(&:id)
|
9
|
+
|
10
|
+
if child_ids.any?
|
11
|
+
self.class.where(post__in: child_ids)
|
12
|
+
else
|
13
|
+
[]
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Rooftop
|
2
|
+
module Rails
|
3
|
+
module Extras
|
4
|
+
# A module to search for free text in a resource. You can add extra fields to search the free text in, by specifying a proc to call (which should return the text you want to search)
|
5
|
+
module ResourceSearch
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
scope :search, ->(q) {
|
10
|
+
all.to_a.select do |item|
|
11
|
+
q = q.gsub(/[^a-zA-z1-9 ]/,"")
|
12
|
+
item.searchable_text.match(%r{#{q.downcase}})
|
13
|
+
end
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
class_methods do
|
18
|
+
|
19
|
+
# A method which takes a lambda which is expected to return a string of text for searching.
|
20
|
+
def add_search_field(field)
|
21
|
+
@search_fields ||= []
|
22
|
+
@search_fields << field
|
23
|
+
end
|
24
|
+
# A collection of lambdas, each of which returns a string
|
25
|
+
def search_fields
|
26
|
+
@search_fields ||= []
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def searchable_text
|
31
|
+
remove_regex = /[^a-zA-z1-9 ]/
|
32
|
+
text = []
|
33
|
+
text << title.gsub(remove_regex,"").downcase
|
34
|
+
text << ActionController::Base.helpers.strip_tags(fields.content).gsub(remove_regex,"").downcase
|
35
|
+
# Iterate over the search field lambdas, and call each one, passing in self. Check the arity is 1, to ensure that the lambda is expecting the object
|
36
|
+
self.class.search_fields.each do |field|
|
37
|
+
if field.arity == 1
|
38
|
+
text << ActionController::Base.helpers.strip_tags(field.call(self)).gsub(remove_regex,"").downcase
|
39
|
+
end
|
40
|
+
end
|
41
|
+
text.join(" ")
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Rooftop
|
2
|
+
class ExtrasGenerator < ::Rails::Generators::Base
|
3
|
+
desc "A wrapper around the common things we do with Rooftop and Rails. Creates a PagesController, Page model, sets up templates as we tend to "
|
4
|
+
|
5
|
+
def create_page_class
|
6
|
+
generate 'rooftop:page'
|
7
|
+
end
|
8
|
+
|
9
|
+
def install_pages_controller
|
10
|
+
generate 'rooftop:pages_controller'
|
11
|
+
end
|
12
|
+
|
13
|
+
def create_content_page_template
|
14
|
+
generate "rooftop:template content_page"
|
15
|
+
end
|
16
|
+
|
17
|
+
def create_post_class
|
18
|
+
generate 'rooftop:post post'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Rooftop
|
2
|
+
class PageGenerator < ::Rails::Generators::Base
|
3
|
+
source_root File.expand_path("../templates", __FILE__)
|
4
|
+
desc "Generate a page class, mixing in Rooftop::Page and Rooftop::Rails::Extras::ResourceSearch"
|
5
|
+
class_option :class_name, :type => :string, :desc => "The class name to generate", :default => "page"
|
6
|
+
def create_class
|
7
|
+
template 'page_class.rb.erb', 'app/models/page.rb'
|
8
|
+
end
|
9
|
+
|
10
|
+
def create_decorator
|
11
|
+
generate "decorator #{options[:class_name]}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Rooftop
|
2
|
+
class PagesControllerGenerator < ::Rails::Generators::Base
|
3
|
+
source_root File.expand_path("../templates", __FILE__)
|
4
|
+
desc "Generate a standardised PagesController and views. Assumes a PageDecorator and Page model"
|
5
|
+
|
6
|
+
def copy_template
|
7
|
+
template "pages_controller.rb.erb", "app/controllers/pages_controller.rb"
|
8
|
+
end
|
9
|
+
|
10
|
+
def create_views
|
11
|
+
template "application.html.erb", "app/views/layouts/application.html.erb"
|
12
|
+
template "show.html.erb", "app/views/pages/show.html.erb"
|
13
|
+
template "index.html.erb", "app/views/pages/index.html.erb"
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_route
|
17
|
+
comment_lines 'config/routes.rb', /pages#.*/
|
18
|
+
route 'root to: "pages#index"'
|
19
|
+
route 'match "/*nested_path", via: [:get], to: "pages#show", as: :page'
|
20
|
+
inject_into_file 'config/routes.rb', before: 'match "/*nested_path"' do <<-'RUBY'
|
21
|
+
# IMPORTANT: this is a greedy catchall route - it needs to be the last route in the file.
|
22
|
+
RUBY
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module Rooftop
|
2
|
+
class PostGenerator < ::Rails::Generators::NamedBase
|
3
|
+
source_root File.expand_path("../templates", __FILE__)
|
4
|
+
desc "Generate a post class - requires the name of the class to create"
|
5
|
+
def create_class
|
6
|
+
template 'post_class.rb.erb', "app/models/#{name}.rb"
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module Rooftop
|
2
|
+
class TemplateGenerator < ::Rails::Generators::NamedBase
|
3
|
+
source_root File.expand_path("../templates", __FILE__)
|
4
|
+
desc "Generate a template in views/layouts/templates"
|
5
|
+
def copy_template
|
6
|
+
template "template.html.erb", "app/views/layouts/templates/#{name}.html.erb"
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title><%= Rails.application.class.parent_name %></title>
|
5
|
+
<%%= stylesheet_link_tag 'application', media: 'all' %>
|
6
|
+
<%%= javascript_include_tag 'application' %>
|
7
|
+
<%%= csrf_meta_tags %>
|
8
|
+
</head>
|
9
|
+
<body>
|
10
|
+
|
11
|
+
<%%= content_for?(:template) ? yield(:template) : yield %>
|
12
|
+
|
13
|
+
</body>
|
14
|
+
</html>
|
@@ -0,0 +1,49 @@
|
|
1
|
+
class PagesController < ApplicationController
|
2
|
+
include Rooftop::Rails::NestedResource
|
3
|
+
nested_rooftop_resource :page
|
4
|
+
decorates_assigned :page, with: PageDecorator
|
5
|
+
|
6
|
+
prepend_before_action :redirect_page_if_required, only: :show
|
7
|
+
prepend_before_action :find_and_validate_page, only: :show
|
8
|
+
|
9
|
+
layout :determine_layout
|
10
|
+
|
11
|
+
# This is the homepage. We're assuming that the slug is 'home'
|
12
|
+
def index
|
13
|
+
@page = Page.find_by(slug: "home").first
|
14
|
+
get_homepage_content
|
15
|
+
end
|
16
|
+
|
17
|
+
# This is the method used to render every page on the site except the homepage
|
18
|
+
def show
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
# If there's a redirect required, do that instead of rendering.
|
25
|
+
def redirect_page_if_required
|
26
|
+
if @page.redirect?
|
27
|
+
redirect_to @page.fields.redirect_url and return
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Determine which layout to show, assuming a standardised approach of having a templates folder under layouts with all the templates in, named as underscored versions of the Rooftop template names.
|
32
|
+
def determine_layout
|
33
|
+
if action_name == "index"
|
34
|
+
"templates/homepage"
|
35
|
+
else
|
36
|
+
if @page.present? && @page.template.present? && template_exists?(@page.template.underscore,File.join("layouts/templates"))
|
37
|
+
"templates/#{@page.template.underscore}"
|
38
|
+
else
|
39
|
+
"templates/content_page"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def get_homepage_content
|
45
|
+
# This is where you put homepage-specific stuff - usually other calls to Rooftop which you assign to instance variables.
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
end
|