duck_map 0.8.0
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/app/controllers/sitemap_base_controller.rb +13 -0
- data/app/controllers/sitemap_controller.rb +3 -0
- data/app/views/sitemap/default_template.xml.erb +10 -0
- data/config/routes.rb +5 -0
- data/lib/duck_map/array_helper.rb +50 -0
- data/lib/duck_map/attributes.rb +182 -0
- data/lib/duck_map/class_helpers.rb +55 -0
- data/lib/duck_map/config.rb +367 -0
- data/lib/duck_map/controller_helpers.rb +206 -0
- data/lib/duck_map/engine.rb +92 -0
- data/lib/duck_map/filter_stack.rb +200 -0
- data/lib/duck_map/handlers/base.rb +102 -0
- data/lib/duck_map/handlers/index.rb +140 -0
- data/lib/duck_map/handlers/show.rb +231 -0
- data/lib/duck_map/last_mod.rb +45 -0
- data/lib/duck_map/list.rb +82 -0
- data/lib/duck_map/logger.rb +193 -0
- data/lib/duck_map/mapper.rb +216 -0
- data/lib/duck_map/model.rb +27 -0
- data/lib/duck_map/route.rb +275 -0
- data/lib/duck_map/route_filter.rb +177 -0
- data/lib/duck_map/route_set.rb +209 -0
- data/lib/duck_map/sitemap_object.rb +457 -0
- data/lib/duck_map/static.rb +146 -0
- data/lib/duck_map/sync.rb +192 -0
- data/lib/duck_map/version.rb +3 -0
- data/lib/duck_map/view_helpers.rb +107 -0
- data/lib/duck_map.rb +3 -0
- data/lib/generators/duckmap/sitemaps/USAGE +23 -0
- data/lib/generators/duckmap/sitemaps/sitemaps_generator.rb +36 -0
- data/lib/generators/duckmap/static/USAGE +47 -0
- data/lib/generators/duckmap/static/static_generator.rb +51 -0
- data/lib/generators/duckmap/sync/USAGE +25 -0
- data/lib/generators/duckmap/sync/sync_generator.rb +29 -0
- metadata +96 -0
@@ -0,0 +1,209 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module DuckMap
|
4
|
+
|
5
|
+
##################################################################################
|
6
|
+
# Mixin module for ActionDispatch::Routing::RouteSet to add support for defining sitemaps directly
|
7
|
+
# inside the config/routes.rb of a Rails app.
|
8
|
+
module RouteSet
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
##################################################################################
|
12
|
+
# Builds a list of routes associated with a sitemap. The actual list of routes returned is based
|
13
|
+
# on {DuckMap::Sitemap::Route::InstanceMethods#sitemap_route_name sitemap_route_name}, which is the named route of the sitemap.
|
14
|
+
# @note See {DuckMap::Sitemap::Mapper::InstanceMethods#sitemap} for a full explanation of how to define a sitemap and how those rules affect this method.
|
15
|
+
# @param [String] name_or_path The request.path of the current sitemap url or the name assigned to the sitemap via config/routes.rb.
|
16
|
+
# @return [Array]
|
17
|
+
def find_sitemap_route(name_or_path)
|
18
|
+
|
19
|
+
name_or_path = name_or_path.to_s
|
20
|
+
|
21
|
+
# strip off the extension if it exists
|
22
|
+
if name_or_path.include?(".")
|
23
|
+
name_or_path = name_or_path.slice(0, name_or_path.rindex("."))
|
24
|
+
end
|
25
|
+
|
26
|
+
full_name = "#{name_or_path}_sitemap"
|
27
|
+
|
28
|
+
# search for a sitemap route matching the path passed to the method.
|
29
|
+
# the route MUST be marked as a sitemap. return nothing if the sitemap route cannot be found.
|
30
|
+
return self.routes.find {|route| route.is_sitemap? &&
|
31
|
+
(route.path.spec.to_s =~ /^#{name_or_path}/ ||
|
32
|
+
route.sitemap_route_name.eql?(name_or_path) ||
|
33
|
+
route.sitemap_route_name.eql?(full_name))}
|
34
|
+
end
|
35
|
+
|
36
|
+
##################################################################################
|
37
|
+
# Builds a list of routes associated with a sitemap route. The actual list of routes returned is based
|
38
|
+
# on {DuckMap::Sitemap::Route::InstanceMethods#sitemap_route_name sitemap_route_name}, which is the named
|
39
|
+
# route of the sitemap.
|
40
|
+
# @note See {DuckMap::Sitemap::Mapper::InstanceMethods#sitemap} for a full explanation of how to define a sitemap and how those rules affect this method.
|
41
|
+
# @param [String] sitemap_route A sitemap route.
|
42
|
+
# @return [Array]
|
43
|
+
def sitemap_routes(sitemap_route)
|
44
|
+
list = []
|
45
|
+
|
46
|
+
if sitemap_route
|
47
|
+
|
48
|
+
# if the sitemap defined within config/routes.rb included a block, then, return ALL of the rows associated with
|
49
|
+
# sitemap route name. Otherwise, return ALL routes that have NOT BEEN associated with any other sitemap.
|
50
|
+
if sitemap_route.sitemap_with_block?
|
51
|
+
|
52
|
+
# sitemap_route_name MUST MATCH
|
53
|
+
#list = self.routes.find_all {|route| !route.is_sitemap? && route.sitemap_route_name.eql?(sitemap_route.sitemap_route_name)}
|
54
|
+
list = self.routes.find_all do |route|
|
55
|
+
!route.is_sitemap? && route.sitemap_route_name.eql?(sitemap_route.sitemap_route_name)
|
56
|
+
end
|
57
|
+
|
58
|
+
else
|
59
|
+
|
60
|
+
candidates = self.routes.find_all {|route| !route.is_sitemap?}
|
61
|
+
|
62
|
+
potential_owners = self.routes.find_all {|route| route.is_sitemap?}
|
63
|
+
potential_owners.sort! { |a,b| b.namespace_prefix_underscores <=> a.namespace_prefix_underscores}
|
64
|
+
|
65
|
+
candidates.each do |candidate|
|
66
|
+
|
67
|
+
value = nil
|
68
|
+
potential_owners.each do |owner|
|
69
|
+
|
70
|
+
if (!candidate.sitemap_route_name.blank? && owner.sitemap_route_name.eql?(candidate.sitemap_route_name))
|
71
|
+
|
72
|
+
value = owner
|
73
|
+
break
|
74
|
+
|
75
|
+
elsif (!owner.namespace_prefix.blank? &&
|
76
|
+
candidate.name =~ /^#{owner.namespace_prefix}/ &&
|
77
|
+
!owner.sitemap_with_block?)
|
78
|
+
|
79
|
+
value = owner
|
80
|
+
break
|
81
|
+
end
|
82
|
+
|
83
|
+
if value.blank?
|
84
|
+
potential_owners.each do |owner|
|
85
|
+
if (owner.namespace_prefix.blank? && !owner.sitemap_with_block?)
|
86
|
+
|
87
|
+
value = owner
|
88
|
+
break
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
if !value.blank? && value.name.eql?(sitemap_route.name)
|
97
|
+
list.push(candidate)
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
list.reject! {|route| !self.include_route?(route)}
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
|
108
|
+
return list
|
109
|
+
end
|
110
|
+
|
111
|
+
##################################################################################
|
112
|
+
def route_owner(route)
|
113
|
+
value = nil
|
114
|
+
|
115
|
+
potential_owners = self.routes.find_all {|route| route.is_sitemap?}
|
116
|
+
potential_owners.sort! { |a,b| b.namespace_prefix_underscores <=> a.namespace_prefix_underscores}
|
117
|
+
|
118
|
+
potential_owners.each do |owner|
|
119
|
+
|
120
|
+
if (!route.sitemap_route_name.blank? && owner.sitemap_route_name.eql?(route.sitemap_route_name))
|
121
|
+
value = owner
|
122
|
+
break
|
123
|
+
elsif !owner.namespace_prefix.blank? && route.name =~ /^#{owner.namespace_prefix}/
|
124
|
+
value = owner
|
125
|
+
break
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
if value.blank?
|
131
|
+
potential_owners.each do |owner|
|
132
|
+
if owner.namespace_prefix.blank? && !owner.sitemap_with_block?
|
133
|
+
value = owner
|
134
|
+
break
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
return value
|
140
|
+
end
|
141
|
+
|
142
|
+
##################################################################################
|
143
|
+
def sitemap_routes_only
|
144
|
+
return self.routes.find_all {|route| route.is_sitemap?}
|
145
|
+
end
|
146
|
+
|
147
|
+
##################################################################################
|
148
|
+
def find_route_via_name(name)
|
149
|
+
return self.routes.find {|route| route.name.eql?(name)}
|
150
|
+
end
|
151
|
+
|
152
|
+
##################################################################################
|
153
|
+
def find_route_via_path(path, environment = {})
|
154
|
+
method = (environment[:method] || "GET").to_s.upcase
|
155
|
+
path = Journey::Router::Utils.normalize_path(path) unless path =~ %r{://}
|
156
|
+
|
157
|
+
begin
|
158
|
+
env = Rack::MockRequest.env_for(path, {:method => method})
|
159
|
+
rescue URI::InvalidURIError => e
|
160
|
+
raise ActionController::RoutingError, e.message
|
161
|
+
end
|
162
|
+
|
163
|
+
req = @request_class.new(env)
|
164
|
+
@router.recognize(req) do |route, matches, params|
|
165
|
+
params.each do |key, value|
|
166
|
+
if value.is_a?(String)
|
167
|
+
value = value.dup.force_encoding(Encoding::BINARY) if value.encoding_aware?
|
168
|
+
params[key] = URI.parser.unescape(value)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
dispatcher = route.app
|
173
|
+
#while dispatcher.is_a?(Mapper::Constraints) && dispatcher.matches?(env) do
|
174
|
+
while dispatcher.is_a?(ActionDispatch::Routing::Mapper::Constraints) && dispatcher.matches?(env) do
|
175
|
+
dispatcher = dispatcher.app
|
176
|
+
end
|
177
|
+
|
178
|
+
if dispatcher.is_a?(ActionDispatch::Routing::RouteSet::Dispatcher) && dispatcher.controller(params, false)
|
179
|
+
dispatcher.prepare_params!(params)
|
180
|
+
return route
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
raise ActionController::RoutingError, "No route matches #{path.inspect}"
|
185
|
+
end
|
186
|
+
|
187
|
+
|
188
|
+
|
189
|
+
|
190
|
+
|
191
|
+
end
|
192
|
+
|
193
|
+
end
|
194
|
+
|
195
|
+
|
196
|
+
|
197
|
+
|
198
|
+
|
199
|
+
|
200
|
+
|
201
|
+
|
202
|
+
|
203
|
+
|
204
|
+
|
205
|
+
|
206
|
+
|
207
|
+
|
208
|
+
|
209
|
+
|
@@ -0,0 +1,457 @@
|
|
1
|
+
# DONE i think. need to proof read and re-verify
|
2
|
+
require 'active_support/concern'
|
3
|
+
|
4
|
+
module DuckMap
|
5
|
+
|
6
|
+
##################################################################################
|
7
|
+
# Module used to add Sitemap methods and attributes to an object.
|
8
|
+
module SitemapObject
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
##################################################################################
|
12
|
+
module ClassMethods
|
13
|
+
|
14
|
+
##################################################################################
|
15
|
+
# @note The syntax is always the same regardless if you define acts_as_sitemap in config/routes.rb or directly
|
16
|
+
# on a controller or model.
|
17
|
+
#
|
18
|
+
# Sets default values, attributes and mappings for sitemap objects. All SitemapObject attributes are maintained
|
19
|
+
# in a master Hash. Attributes for each action are stored in the master Hash and accessed via action_name as the key.
|
20
|
+
#
|
21
|
+
# All attributes are broken into two categories.
|
22
|
+
# - Configuration level: These attributes are defined within the config/routes.rb block and are considered
|
23
|
+
# global default values for ALL sitemap related classes and objects.
|
24
|
+
# - Controller level: These attributes are exactly the same as Configuration level with one difference.
|
25
|
+
# They apply to each controller class ONLY. Setting attributes at the controller level will make a copy
|
26
|
+
# of the Configuration level attributes and merge with the values you pass to {#acts_as_sitemap acts_as_sitemap}.
|
27
|
+
#
|
28
|
+
# When a sitemap is created or meta tag data is needed, the controller is "asked" to provide the required data
|
29
|
+
# for a specific action on the controller. The mechanism that makes the request is called a "handler". Handlers
|
30
|
+
# are mapped to standard Rails controller actions.
|
31
|
+
# - The index action is mapped to sitemap_index.
|
32
|
+
# - The show action is mapped to sitemap_show.
|
33
|
+
#
|
34
|
+
# Attributes have two meanings:
|
35
|
+
# - If the attribute value is set to a Symbol, then, it is considered to point to an attribute/method name of the target SitemapObject (self).
|
36
|
+
# - If the attribute value is set to anything other than a Symbol, then, the value is used "as is".
|
37
|
+
#
|
38
|
+
# In the example below, we are setting meta data attributes.
|
39
|
+
#
|
40
|
+
# acts_as_sitemap :index, title: "List of my pages..." #=> static title for the index page.
|
41
|
+
# acts_as_sitemap :edit, title: :title #=> returns the value of the title attribute/method.
|
42
|
+
# acts_as_sitemap :new, title: "New page" #=> static title for the new page.
|
43
|
+
# acts_as_sitemap :show, title: :title #=> returns the value of the title attribute/method.
|
44
|
+
#
|
45
|
+
# Here, we are setting sitemap attributes.
|
46
|
+
#
|
47
|
+
# acts_as_sitemap :index, changefreq: "always", priority: "1.0"
|
48
|
+
# acts_as_sitemap :edit, changefreq: "never", priority: "0.0"
|
49
|
+
# acts_as_sitemap :new, changefreq: "never", priority: "0.0"
|
50
|
+
# acts_as_sitemap :show, changefreq: "monthly", priority: "7.0"
|
51
|
+
#
|
52
|
+
# # here we are telling ALL handlers to get the last modified date
|
53
|
+
# # for a sitemap and meta tag from
|
54
|
+
# acts_as_sitemap lastmod: :last_updated_at
|
55
|
+
#
|
56
|
+
# The sitemap / meta tag {DuckMap::Handlers handlers} will attempt to extract attributes values from the first model
|
57
|
+
# found on a controller. To make meeting special needs a little easier, you can use a block to return a single model
|
58
|
+
# or an Array of model objects to include in your sitemap.
|
59
|
+
#
|
60
|
+
# This example is worth a few extra words.
|
61
|
+
# - When building a sitemap for the index action of a controller, you only need to build one url.
|
62
|
+
# - When building a sitemap for the show action of a controller, you will typically need to build an entire set of urls to represent
|
63
|
+
# the rows in your table. However, when showing the meta tag data, you will need the attributes from the specific model for the current
|
64
|
+
# page.
|
65
|
+
#
|
66
|
+
# acts_as_sitemap :index do
|
67
|
+
# Book.where(title: "my latest book").first
|
68
|
+
# end
|
69
|
+
#
|
70
|
+
# acts_as_sitemap :show do
|
71
|
+
# Book.all
|
72
|
+
# end
|
73
|
+
#
|
74
|
+
# Let's say you the show action of your controller just won't fit into the standard behavior
|
75
|
+
# and you need to build the url using a custom method. Simply build the method on your object and point to it.
|
76
|
+
#
|
77
|
+
# acts_as_sitemap :show, canonical: :my_special_method
|
78
|
+
#
|
79
|
+
# The full syntax would include handlers and segments.
|
80
|
+
#
|
81
|
+
# acts_as_sitemap :index, title: "My App",
|
82
|
+
# changefreq: "always",
|
83
|
+
# priority: "1.0",
|
84
|
+
# handler: {action_name: :my_index, first_model: false},
|
85
|
+
# segments: {id: :my_id, sub_id: :belongs_to_id}
|
86
|
+
#
|
87
|
+
# However, there are convenience methods to make things a little cleaner.
|
88
|
+
#
|
89
|
+
# acts_as_sitemap :index, title: "My App", changefreq: "always", priority: "1.0"
|
90
|
+
# sitemap_handler :index, action_name: :my_index, first_model: false
|
91
|
+
# sitemap_segments :index, id: :my_id, sub_id: :belongs_to_id
|
92
|
+
#
|
93
|
+
# So far, we have only discussed the attributes that are used by the standard {DuckMap::Handlers handlers}. However,
|
94
|
+
# you do have the option of building and using your own handlers to meet special needs or to completely change the default
|
95
|
+
# behavior of ALL handlers. Let's say the default behavior for the {DuckMap::Handlers::Index index handler} does not meet your
|
96
|
+
# needs. You could build your own index handler and set it in the config/routes.rb
|
97
|
+
#
|
98
|
+
# MyApp::Application.routes.draw do
|
99
|
+
#
|
100
|
+
# # defining attributes in config/routes.rb are used globally
|
101
|
+
# # your action handler method will be used throughout the entire app.
|
102
|
+
# # simply create your own handler method and make it accessible to every controller
|
103
|
+
# # in your app. Maybe include in your ApplicationController or something.
|
104
|
+
# sitemap_handler :index, action_name: :my_index
|
105
|
+
#
|
106
|
+
# end
|
107
|
+
#
|
108
|
+
# @overload acts_as_sitemap(action_name = nil, options = {}, &block)
|
109
|
+
# @param [String, Symbol] action_name The action name is used as a key when looking for attributes.
|
110
|
+
# When sitemap or meta tag needs data for the index action it
|
111
|
+
# will use :index as the key. Omitting the action_name will
|
112
|
+
# default to :all which will assign the values you pass to "all"
|
113
|
+
# actions for the controller.
|
114
|
+
#
|
115
|
+
#
|
116
|
+
# @param [Hash] options Options hash.
|
117
|
+
# - Setting to a Symbol will attempt to access attribute method name on the SitemapObject itself.
|
118
|
+
# - Setting to any value other than a Symbol will use that value "as is".
|
119
|
+
# @option options [Symbol] :canonical Default value: nil
|
120
|
+
# @option options [Symbol] :canonical_host Default value: nil
|
121
|
+
# @option options [Symbol] :changefreq Valid static values are:
|
122
|
+
# - always
|
123
|
+
# - hourly
|
124
|
+
# - daily
|
125
|
+
# - weekly
|
126
|
+
# - monthly
|
127
|
+
# - yearly
|
128
|
+
# - never
|
129
|
+
# @option options [Symbol] :description Default value: :description
|
130
|
+
# @option options [Symbol] :handler Sub-hash containing attributes for the handler. See {#sitemap_handler sitemap_handler}
|
131
|
+
# @option options [Symbol] :keywords Default value: :keywords
|
132
|
+
# @option options [Symbol] :lastmod Default value: :updated_at
|
133
|
+
# @option options [Symbol] :priority Valid static values range from 0.0 to 1.0
|
134
|
+
# @option options [Symbol] :segments Sub-hash containing attributes for the segments. See {#segments_handler segments_handler}
|
135
|
+
# @option options [Symbol] :title Default value: :title
|
136
|
+
# @option options [Symbol] :url_format Default value: "html"
|
137
|
+
# @option options [Symbol] :url_limit Default value: 50000
|
138
|
+
#
|
139
|
+
# @param [Block] block A block to assign to all handlers associated with action_name. The
|
140
|
+
# block should return an Array of ActiveRecord::Base models. The model objects
|
141
|
+
# server as sitemap content for the given action name.
|
142
|
+
# @return [Nil]
|
143
|
+
def acts_as_sitemap(*args, &block)
|
144
|
+
key = :all
|
145
|
+
|
146
|
+
self.sitemap_attributes_defined = true
|
147
|
+
|
148
|
+
if args.length > 0
|
149
|
+
|
150
|
+
if args.first.kind_of?(Symbol)
|
151
|
+
key = args.shift
|
152
|
+
|
153
|
+
elsif args.first.kind_of?(String)
|
154
|
+
key = args.shift.to_sym
|
155
|
+
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
159
|
+
|
160
|
+
values = args.first.kind_of?(Hash) ? args.first : {}
|
161
|
+
|
162
|
+
# delete method should always return nil if the key doesn't exist
|
163
|
+
handler = values.delete(:handler)
|
164
|
+
segments = values.delete(:segments)
|
165
|
+
|
166
|
+
# i'm going to verify and set a default anyway
|
167
|
+
handler = handler.kind_of?(Hash) ? handler : {}
|
168
|
+
|
169
|
+
segments = segments.kind_of?(Hash) ? segments : {}
|
170
|
+
|
171
|
+
# build a list of keys to work on
|
172
|
+
# developer can specify :all, which will build a list of all the existing keys in the Hash
|
173
|
+
# otherwise, it will just use the key passed to acts_as_sitemap
|
174
|
+
keys = []
|
175
|
+
if key == :all
|
176
|
+
self.sitemap_attributes.each {|pair| keys.push(pair.first)}
|
177
|
+
else
|
178
|
+
keys.push(key)
|
179
|
+
end
|
180
|
+
|
181
|
+
# process all of the keys in the list.
|
182
|
+
keys.each do |key|
|
183
|
+
|
184
|
+
# create defaults unless they exist
|
185
|
+
unless self.sitemap_attributes[key].kind_of?(Hash)
|
186
|
+
self.sitemap_attributes[key] = {}
|
187
|
+
end
|
188
|
+
|
189
|
+
unless self.sitemap_attributes[key][:handler].kind_of?(Hash)
|
190
|
+
self.sitemap_attributes[key][:handler] = {}
|
191
|
+
end
|
192
|
+
|
193
|
+
unless self.sitemap_attributes[key][:segments].kind_of?(Hash)
|
194
|
+
self.sitemap_attributes[key][:segments] = {}
|
195
|
+
end
|
196
|
+
|
197
|
+
# merge the main hash.
|
198
|
+
self.sitemap_attributes[key].merge!(values)
|
199
|
+
|
200
|
+
# merge the handler
|
201
|
+
#unless handler.blank?
|
202
|
+
if handler.kind_of?(Hash)
|
203
|
+
|
204
|
+
if block_given?
|
205
|
+
|
206
|
+
handler[:block] = block
|
207
|
+
end
|
208
|
+
|
209
|
+
self.sitemap_attributes[key][:handler].merge!(handler)
|
210
|
+
|
211
|
+
end
|
212
|
+
|
213
|
+
# merge the segments
|
214
|
+
unless segments.blank?
|
215
|
+
|
216
|
+
self.sitemap_attributes[key][:segments].merge!(segments)
|
217
|
+
|
218
|
+
end
|
219
|
+
|
220
|
+
end
|
221
|
+
|
222
|
+
return nil
|
223
|
+
end
|
224
|
+
|
225
|
+
##################################################################################
|
226
|
+
# @note See {DuckMap::Handlers} for specific details on how each one of these attributes
|
227
|
+
# are utilized by each handler.
|
228
|
+
#
|
229
|
+
# Wrapper for {#acts_as_sitemap acts_as_sitemap} and accepts the same exact arguments.
|
230
|
+
# Basically, sitemap_handler simplifies the syntax needed to define a handler
|
231
|
+
# for all or a specific action.
|
232
|
+
#
|
233
|
+
# sitemap_handler :index, action_name: :my_index, first_model: false
|
234
|
+
#
|
235
|
+
# # is equivalent to:
|
236
|
+
# acts_as_sitemap :index, handler: {action_name: :my_index}
|
237
|
+
#
|
238
|
+
# sitemap_handler :index, instance_var: :my_var
|
239
|
+
# sitemap_handler :index, instance_var: "my_var"
|
240
|
+
#
|
241
|
+
# sitemap_handler :index, model: Book
|
242
|
+
# sitemap_handler :index, model: "Book"
|
243
|
+
#
|
244
|
+
#
|
245
|
+
# @overload sitemap_handler(action_name = nil, options = {}, &block)
|
246
|
+
# @param [String, Symbol] action_name The action name. See {#acts_as_sitemap acts_as_sitemap}
|
247
|
+
# @param [Hash] options Options hash.
|
248
|
+
# @option options [Symbol] :action_name The method name to call. Method name MUST exist on your object!
|
249
|
+
# @option options [Symbol] :first_model Boolean. Tells the handler to get values from the first available
|
250
|
+
# model object found on the controller.
|
251
|
+
# @option options [Symbol] :instance_var The model
|
252
|
+
# @option options [Symbol] :model A model object expressed as a String or an actual class reference.
|
253
|
+
# See {DuckMap::Handlers} for details on how this attribute is utilized.
|
254
|
+
# @return [NilClass]
|
255
|
+
def sitemap_handler(*args, &block)
|
256
|
+
key = args.first.kind_of?(Symbol) ? args.shift : :all
|
257
|
+
options = args.first.kind_of?(Hash) ? args.shift : {}
|
258
|
+
options = args.last.kind_of?(Hash) ? args.pop : options
|
259
|
+
return self.acts_as_sitemap(key, handler: options, &block)
|
260
|
+
end
|
261
|
+
|
262
|
+
##################################################################################
|
263
|
+
# Wrapper for {#acts_as_sitemap acts_as_sitemap} and accepts the same exact arguments.
|
264
|
+
# Basically, sitemap_segments simplifies the syntax needed to define segments
|
265
|
+
# for all or a specific action.
|
266
|
+
#
|
267
|
+
# sitemap_segments :index, id: :my_id
|
268
|
+
#
|
269
|
+
# # is equivalent to:
|
270
|
+
# acts_as_sitemap :index, segments: {id: :my_id}
|
271
|
+
#
|
272
|
+
# @return [NilClass]
|
273
|
+
def sitemap_segments(*args, &block)
|
274
|
+
key = args.first.kind_of?(Symbol) ? args.shift : :all
|
275
|
+
options = args.first.kind_of?(Hash) ? args.shift : {}
|
276
|
+
options = args.last.kind_of?(Hash) ? args.pop : options
|
277
|
+
return self.acts_as_sitemap(key, segments: options, &block)
|
278
|
+
end
|
279
|
+
|
280
|
+
end
|
281
|
+
|
282
|
+
##################################################################################
|
283
|
+
# Returns a Hash containing key/value pairs from the current object.
|
284
|
+
#
|
285
|
+
# This method loops through all of the key/value pairs contained in the attributes Hash.
|
286
|
+
#
|
287
|
+
# Each pair is inspected.
|
288
|
+
# - if the value is a Symbol
|
289
|
+
# - the current object is asked if it has a matching method name matching the value.
|
290
|
+
# - if true, then, the method is called to obtain a value.
|
291
|
+
# - if the new value is NOT nil, then, it is paired with the key of the current pair being processed
|
292
|
+
# and assigned to the returning Hash.
|
293
|
+
# - otherwise, if the value is not nil, then, it is considered to be a static value and is assigned to the
|
294
|
+
# returning Hash using the key of the current pair being processed.
|
295
|
+
#
|
296
|
+
# # let's pretend this class exists and has the following attributes.
|
297
|
+
# object = MyObject.new
|
298
|
+
# object.my_title = "my title"
|
299
|
+
# object.tags = "this that other"
|
300
|
+
# object.content = "this is a test"
|
301
|
+
# object.last_updated_at = Time.now
|
302
|
+
#
|
303
|
+
# attributes = {title: :my_title,
|
304
|
+
# keywords: nil,
|
305
|
+
# description: "this is my desc",
|
306
|
+
# lastmod: :invalid_method_name}
|
307
|
+
#
|
308
|
+
# values = object.sitemap_capture_attributes(attributes)
|
309
|
+
#
|
310
|
+
# puts YAML.dump values # => {title => "my title", description => "this is my desc"}
|
311
|
+
#
|
312
|
+
# The result:
|
313
|
+
# - :my_title is a Symbol and attribute exists on object and value is not nil, so, it is included in the returning Hash.
|
314
|
+
# - :keywords is nil, so, it is ignored and not included in the returning Hash.
|
315
|
+
# - :description is a String and considered a static value, so, it is included in the returning Hash "as is".
|
316
|
+
# - :my_title is a Symbol, however, a matching attribute/method does NOT exist on the target object, so,
|
317
|
+
# it is ignored and not included in the returning Hash.
|
318
|
+
#
|
319
|
+
# @param [Hash] options Options hash. Can be any combination of key/value pairs (one-dimensional).
|
320
|
+
# return [Hash]
|
321
|
+
def sitemap_capture_attributes(attributes = {})
|
322
|
+
values = {}
|
323
|
+
|
324
|
+
attributes.each do |pair|
|
325
|
+
|
326
|
+
# if the value of the pair is a Symbol, then, it is implied to be
|
327
|
+
# an attribute of the object we are working on.
|
328
|
+
# therefore, we will attempt to get the value of that attribute from the object.
|
329
|
+
if pair.last.kind_of?(Symbol)
|
330
|
+
|
331
|
+
# if the object has the attribute/method we are looking for, then, capture the value
|
332
|
+
# by calling the attribute/method and assign the return value to the return Hash
|
333
|
+
# using pair.first as the key.
|
334
|
+
if self.respond_to?(pair.last)
|
335
|
+
|
336
|
+
# ask the object for the attribute value
|
337
|
+
value = self.send(pair.last)
|
338
|
+
|
339
|
+
# ignore nil values
|
340
|
+
unless value.blank?
|
341
|
+
values[pair.first] = value
|
342
|
+
end
|
343
|
+
|
344
|
+
end
|
345
|
+
|
346
|
+
elsif !pair.last.blank?
|
347
|
+
values[pair.first] = pair.last
|
348
|
+
end
|
349
|
+
|
350
|
+
end
|
351
|
+
|
352
|
+
return values
|
353
|
+
end
|
354
|
+
|
355
|
+
##################################################################################
|
356
|
+
# Segment keys are placeholders for the values that are plugged into a named route when it is constructed.
|
357
|
+
#
|
358
|
+
# The following Rails route has a two segment keys: :id and :format.
|
359
|
+
#
|
360
|
+
# book GET /books/:id(.:format) books#show
|
361
|
+
#
|
362
|
+
# :id is the row.id of a Book and :format is the extension to be used when constructing a path or url.
|
363
|
+
#
|
364
|
+
# book_path(1) #=> /book/1
|
365
|
+
# book_path(1, "html") #=> /book/1.html
|
366
|
+
# book_path(id: 1, format: "html") #=> /book/1.html
|
367
|
+
# book_path(id: 2, format: "xml") #=> /book/2.xml
|
368
|
+
#
|
369
|
+
# sitemap_capture_segments attempts to populate a Hash with values associated with the required segment keys.
|
370
|
+
#
|
371
|
+
# row = Book.create(title: "Duck is a self-proclaimed resident moron...")
|
372
|
+
# puts row.id #=> 1
|
373
|
+
# row.sitemap_capture_segments(nil, [:id]) #=> {:id => 1}
|
374
|
+
#
|
375
|
+
# You have the ability to map attributes of an object to segment keys. This could be useful for routes
|
376
|
+
# that do not follow standard convention or cases where you have some deeply nested resources.
|
377
|
+
#
|
378
|
+
# class BooksController < ApplicationController
|
379
|
+
# sitemap_segments :show, id: :my_id
|
380
|
+
# end
|
381
|
+
#
|
382
|
+
# class Book < ActiveRecord::Base
|
383
|
+
# attr_accessible :my_id, :author, :title
|
384
|
+
#
|
385
|
+
# before_save :generate_my_id
|
386
|
+
#
|
387
|
+
# def generate_my_id
|
388
|
+
# # do some magic
|
389
|
+
# self.my_id = 2
|
390
|
+
# end
|
391
|
+
#
|
392
|
+
# end
|
393
|
+
#
|
394
|
+
# row = Book.create(title: "Please ignore the first title :)")
|
395
|
+
# puts row.id #=> 1
|
396
|
+
# puts row.my_id #=> 2
|
397
|
+
# # normally, you would get the attributes via:
|
398
|
+
# # controller.sitemap_attributes("show")[:segments]
|
399
|
+
# attributes = {id: :my_id}
|
400
|
+
# row.sitemap_capture_segments(attributes, [:id]) #=> {:id => 2}
|
401
|
+
#
|
402
|
+
# Segment values are obtained in two stages.
|
403
|
+
# - Stage one asks the current object (controller or model) for attributes from segment_mappings and
|
404
|
+
# places those key/values in the returning hash.
|
405
|
+
#
|
406
|
+
# - Stage two asks the current object (controller or model) for attributes from segments array
|
407
|
+
# that have not already been found via segment_mappings and places those key/values in the returning hash.
|
408
|
+
#
|
409
|
+
# @param [Hash] segment_mappings A Hash containing one-to-one attribute mappings for segment keys to object attributes.
|
410
|
+
# @param [Array] segments The segments Array of a Rails Route.
|
411
|
+
# return [Hash]
|
412
|
+
def sitemap_capture_segments(segment_mappings = {}, segments = [])
|
413
|
+
values = {}
|
414
|
+
|
415
|
+
# do nothing if there are no segments to work on
|
416
|
+
if segments.kind_of?(Array)
|
417
|
+
|
418
|
+
# first, look for mappings
|
419
|
+
unless segment_mappings.blank?
|
420
|
+
segments.each do |key|
|
421
|
+
|
422
|
+
attribute_name = segment_mappings[key.to_sym].blank? ? key : segment_mappings[key.to_sym]
|
423
|
+
|
424
|
+
if self.respond_to?(attribute_name)
|
425
|
+
values[key] = self.send(attribute_name)
|
426
|
+
end
|
427
|
+
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
# second, look for attributes that have not already been found.
|
432
|
+
segments.each do |key|
|
433
|
+
|
434
|
+
unless values.has_key?(key)
|
435
|
+
if self.respond_to?(key)
|
436
|
+
values[key] = self.send(key)
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
440
|
+
end
|
441
|
+
|
442
|
+
end
|
443
|
+
|
444
|
+
return values
|
445
|
+
end
|
446
|
+
|
447
|
+
end
|
448
|
+
end
|
449
|
+
|
450
|
+
|
451
|
+
|
452
|
+
|
453
|
+
|
454
|
+
|
455
|
+
|
456
|
+
|
457
|
+
|