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.
@@ -0,0 +1,206 @@
1
+ require 'active_support/concern'
2
+
3
+ module DuckMap
4
+
5
+ ##################################################################################
6
+ # NOT empty anymore...
7
+ # This module is empty on purpose. Sitemap::Mapper will create a sitemap method on this
8
+ # module when a sitemap is actually created via config/routes.rb. This module is included in the SitemapBaseController
9
+ # and therefore will have all of the methods defined via config/routes.rb.
10
+ # Originally, methods were defined directly on SitemapBaseController, however, it was creating
11
+ # some wierd problems during development. Meaning, if you tried to edit and refresh (Rails standard)
12
+ # the Rails stack would blow up. Adding the methods to a module and including the module seemed to fix
13
+ # the problem.
14
+ module SitemapControllerHelpers
15
+
16
+ ##################################################################################
17
+ def sitemap_build(request_path = nil)
18
+
19
+ self.sitemap_model = []
20
+
21
+ begin
22
+
23
+ request_path = request_path.blank? ? request.path : request_path
24
+
25
+ sitemap_route = Rails.application.routes.find_sitemap_route(request_path)
26
+ unless sitemap_route.blank?
27
+ Rails.application.routes.sitemap_routes(sitemap_route).each do |route|
28
+
29
+ begin
30
+
31
+ DuckMap.logger.info "processing route: name: #{route.name} controller: #{route.controller_name} action: #{route.action_name}"
32
+
33
+ clazz = ClassHelpers.get_controller_class(route.controller_name)
34
+
35
+ if clazz.blank?
36
+ DuckMap.logger.debug "sorry, could not determine controller class...: route name: #{route.name} controller: #{route.controller_name} action: #{route.action_name}"
37
+ else
38
+
39
+ controller_object = clazz.new
40
+ controller_object.request = request
41
+ controller_object.response = ActionDispatch::Response.new
42
+
43
+ begin
44
+
45
+ options = { action_name: route.action_name,
46
+ controller_name: route.controller_name,
47
+ model: ClassHelpers.get_model_class(route.controller_name),
48
+ route: route,
49
+ source: :sitemap}
50
+
51
+ rows = controller_object.sitemap_setup(options)
52
+ if rows.kind_of?(Array) && rows.length > 0
53
+ self.sitemap_model.concat(rows)
54
+ end
55
+
56
+ rescue Exception => e
57
+ DuckMap.logger.exception(e)
58
+ end
59
+
60
+ end
61
+
62
+ rescue Exception => e
63
+ DuckMap.logger.exception(e)
64
+ end
65
+
66
+ end
67
+ end
68
+
69
+ self.sitemap_model.each do |item|
70
+ unless item[:lastmod].kind_of?(String)
71
+ item[:lastmod] = item[:lastmod].to_s(:sitemap)
72
+ end
73
+ end
74
+
75
+ rescue Exception => e
76
+ DuckMap.logger.exception(e)
77
+ end
78
+
79
+ return nil
80
+ end
81
+
82
+ end
83
+
84
+ ##################################################################################
85
+ module ControllerHelpers
86
+ extend ActiveSupport::Concern
87
+
88
+ ##################################################################################
89
+ # Determines all of the attributes defined for a controller, then, calls the handler method on the controller
90
+ # to generate and return an Array of Hashes representing all of the url nodes to be included in the sitemap
91
+ # for the current route being processed.
92
+ # @return [Array] An Array of Hashes.
93
+ def sitemap_setup(options = {})
94
+ rows = []
95
+
96
+ DuckMap.logger.debug "sitemap_setup: action_name => #{options[:action_name]} source => #{options[:source]} model => #{options[:model]}"
97
+
98
+ attributes = self.sitemap_attributes(options[:action_name])
99
+
100
+ DuckMap.logger.debug "sitemap_setup: attributes => #{attributes}"
101
+
102
+ if attributes.kind_of?(Hash) && attributes[:handler].kind_of?(Hash) && !attributes[:handler][:action_name].blank?
103
+ config = {handler: attributes[:handler]}.merge(options)
104
+ rows = self.send(attributes[:handler][:action_name], config)
105
+ end
106
+
107
+ return rows
108
+ end
109
+
110
+ ##################################################################################
111
+ # Returns the current value of the instance variable {#sitemap_meta_data}.
112
+ def sitemap_meta_data
113
+ unless defined?(@sitemap_meta_data)
114
+ @sitemap_meta_data = {}
115
+ self.sitemap_setup({action_name: action_name,
116
+ controller_name: controller_name,
117
+ route: nil,
118
+ source: :meta_data})
119
+ end
120
+ return @sitemap_meta_data
121
+ end
122
+
123
+ def sitemap_meta_data=(value)
124
+ @sitemap_meta_data = value
125
+ end
126
+
127
+ ##################################################################################
128
+ def find_first_model_object
129
+ model_object = self.find_model_object
130
+
131
+ if model_object.kind_of?(Array) && model_object.first.kind_of?(ActiveRecord::Base)
132
+ model_object = model_object.first
133
+ end
134
+
135
+ return model_object
136
+ end
137
+
138
+ ##################################################################################
139
+ def find_model_object
140
+ model_object = nil
141
+ candidate = nil
142
+ skip_vars = ["@_config",
143
+ "@view_renderer",
144
+ "@_routes",
145
+ "@_assigns",
146
+ "@_request",
147
+ "@view_flow",
148
+ "@output_buffer",
149
+ "@virtual_path"]
150
+
151
+ list = self.instance_variables.map.find_all {|x| !skip_vars.include?(x.to_s)}
152
+
153
+ list.each do |obj_sym|
154
+ obj = self.instance_variable_get(obj_sym)
155
+ if obj
156
+ if obj.kind_of?(ActiveRecord::Base)
157
+ model_object = obj
158
+ break
159
+ elsif obj.kind_of?(Array) &&
160
+ obj.first.kind_of?(ActiveRecord::Base) &&
161
+ candidate.blank?
162
+ candidate = obj
163
+ end
164
+ end
165
+ end
166
+
167
+ if model_object.blank? && !candidate.blank?
168
+ model_object = candidate
169
+ end
170
+
171
+ return model_object
172
+ end
173
+
174
+ ##################################################################################
175
+ # Returns the date / time value from config/locale/sitemap.yml associated with the current controller / action.
176
+ # The static date/times are actual timestamps extracted from the date of the view on disk and from a .git repository if used.
177
+ # Be sure to run the generator or rake task: duck_map:sync to populate the locale file at: config/locale/sitemap.yml
178
+ # @return [DateTime] The timestamp associated with the controller / action.
179
+ def sitemap_static_lastmod(my_controller_name = nil, my_action_name = nil)
180
+ value = nil
181
+
182
+ unless my_controller_name.blank? || my_action_name.blank?
183
+
184
+ unless my_controller_name.blank?
185
+ my_controller_name = my_controller_name.underscore
186
+ my_controller_name = my_controller_name.gsub("/", ".")
187
+ end
188
+
189
+ begin
190
+
191
+ value = I18n.t("#{my_controller_name.downcase}.#{my_action_name.downcase}", default: "", locale: :sitemap)
192
+
193
+ rescue Exception => e
194
+ DuckMap.logger.exception(e)
195
+ end
196
+
197
+ value = value.blank? ? nil : LastMod.to_date(value)
198
+
199
+ end
200
+
201
+ return value
202
+ end
203
+
204
+ end
205
+
206
+ end
@@ -0,0 +1,92 @@
1
+ require 'journey'
2
+
3
+ module DuckMap
4
+ extend ActiveSupport::Autoload
5
+
6
+ eager_autoload do
7
+
8
+ autoload :ActionViewHelpers, 'duck_map/view_helpers'
9
+ autoload :ArrayHelper, 'duck_map/array_helper'
10
+ autoload :Attributes, 'duck_map/attributes'
11
+ autoload :ClassHelpers, 'duck_map/class_helpers'
12
+ autoload :Config, 'duck_map/config'
13
+ autoload :ConfigHelpers, 'duck_map/config'
14
+ autoload :ControllerHelpers, 'duck_map/controller_helpers'
15
+ autoload :Expressions, 'duck_map/expressions'
16
+ autoload :FilterStack, 'duck_map/filter_stack'
17
+ autoload :Handlers, 'duck_map/handlers/base'
18
+ autoload :InheritableClassAttributes, 'duck_map/attributes'
19
+ autoload :LastMod, 'duck_map/last_mod'
20
+ autoload :List, 'duck_map/list'
21
+ autoload :Logger, 'duck_map/logger'
22
+ autoload :Mapper, 'duck_map/mapper'
23
+ autoload :Model, 'duck_map/model'
24
+ autoload :Route, 'duck_map/route'
25
+ autoload :RouteFilter, 'duck_map/route_filter'
26
+ autoload :RouteSet, 'duck_map/route_set'
27
+ autoload :SitemapControllerHelpers, 'duck_map/controller_helpers'
28
+ autoload :SitemapHelpers, 'duck_map/view_helpers'
29
+ autoload :SitemapObject, 'duck_map/sitemap_object'
30
+ autoload :Static, 'duck_map/static'
31
+ autoload :Sync, 'duck_map/sync'
32
+ autoload :Version, 'duck_map/version'
33
+
34
+ end
35
+
36
+ class DuckMapEngine < Rails::Engine
37
+
38
+ # # this is so I can develop the gem
39
+ # # run dev.com, make changes to files in lib/duck_captcha, refresh browser.
40
+ # unless Rails.env.to_sym.eql?(:production)
41
+ # auto_path_spec = File.expand_path(File.dirname(__FILE__))
42
+ # config.autoload_paths << auto_path_spec.slice(0, auto_path_spec.rindex("/"))
43
+ # end
44
+
45
+ initializer "duck_map" do
46
+ Time::DATE_FORMATS.merge!(meta: "%a, %d %b %Y %H:%M:%S %Z",
47
+ sitemap: "%Y-%m-%dT%H:%M:%S+00:00",
48
+ sitemap_locale: "%m/%d/%Y %H:%M:%S",
49
+ seo: "%a, %d %b %Y %H:%M:%S %Z")
50
+ end
51
+
52
+ Journey::Route.send :include, Route
53
+
54
+ ActionDispatch::Routing::RouteSet.send :include, RouteSet
55
+ ActionDispatch::Routing::RouteSet.send :include, RouteFilter
56
+
57
+ ActionDispatch::Routing::Mapper.send :include, ConfigHelpers
58
+ ActionDispatch::Routing::Mapper.send :include, Mapper
59
+ ActionDispatch::Routing::Mapper.send :include, MapperMethods
60
+
61
+ ActiveSupport.on_load(:active_record) do
62
+ ActiveRecord::Base.send :include, InheritableClassAttributes
63
+ ActiveRecord::Base.send :include, Attributes
64
+ ActiveRecord::Base.send :include, SitemapObject
65
+ end
66
+
67
+ ActiveSupport.on_load(:action_controller) do
68
+ ActionController::Base.send :include, InheritableClassAttributes
69
+ ActionController::Base.send :include, Attributes
70
+ ActionController::Base.send :include, ControllerHelpers
71
+ ActionController::Base.send :include, Handlers::Base
72
+ ActionController::Base.send :include, Handlers::Index
73
+ ActionController::Base.send :include, Handlers::Show
74
+ ActionController::Base.send :include, Model
75
+ ActionController::Base.send :include, SitemapObject
76
+ end
77
+
78
+ ActiveSupport.on_load(:action_view) do
79
+ ActionView::Base.send :include, ActionViewHelpers
80
+ end
81
+
82
+ end
83
+
84
+ def self.logger
85
+ return Logger.logger
86
+ end
87
+
88
+ def self.console(msg)
89
+ logger.console msg
90
+ end
91
+
92
+ end
@@ -0,0 +1,200 @@
1
+ # DONE
2
+ require 'active_support/concern'
3
+
4
+ module DuckMap
5
+
6
+ ##################################################################################
7
+ class FilterStack
8
+
9
+ #DEFAULT_FILTER = {actions: [:new, :create, :edit, :update, :destroy],
10
+ #verbs: [:post, :put, :delete], names: [], controllers: []}
11
+ DEFAULT_FILTER = {actions: [:index, :show], verbs: [], names: [], controllers: []}
12
+
13
+ ##################################################################################
14
+ def initialize
15
+ super
16
+ self.reset
17
+ end
18
+
19
+ ##################################################################################
20
+ # A Hash containing all of the values for exlude sitemap_filters.
21
+ # @return [Hash]
22
+ def stack
23
+ return @stack ||= []
24
+ end
25
+
26
+ ##################################################################################
27
+ # Sets the entire Hash for exclude sitemap_filters.
28
+ # @return [Nil]
29
+ def stack=(value)
30
+ @stack = value
31
+ end
32
+
33
+ ##################################################################################
34
+ # Resets the stack.
35
+ # @return [NilClass]
36
+ def reset
37
+ self.stack = [copy_filter(DEFAULT_FILTER)]
38
+ end
39
+
40
+ ##################################################################################
41
+ # Copies a filter
42
+ # @return [Hash]
43
+ def copy_filter(filter)
44
+ buffer = {}
45
+ filter.each do |part|
46
+ buffer[part[0]] = part[1].dup
47
+ end
48
+ return buffer
49
+ end
50
+
51
+ ##################################################################################
52
+ # Pushes a copy of the current filter onto the end of the stack.
53
+ # @return [Hash]
54
+ def push
55
+ self.stack.push(copy_filter(self.current_filter))
56
+ end
57
+
58
+ ##################################################################################
59
+ # Pops the last item from the stack. However, the list can never be empty, so, it pops
60
+ # the last item ONLY if the stack has more than ONE item.
61
+ # @return [Hash]
62
+ def pop
63
+ return self.stack.length > 1 ? self.stack.pop : self.current_filter
64
+ end
65
+
66
+ ##################################################################################
67
+ # Returns the current filter. The current filter is ALWAYS the last item in the stack.
68
+ # @return [Hash]
69
+ def current_filter
70
+ return self.stack.last
71
+ end
72
+
73
+ ##################################################################################
74
+ # Sets the entire Hash for the {#current_filter}.
75
+ # @return [NilClass]
76
+ def current_filter=(value)
77
+ self.stack.pop
78
+ self.stack.push(value)
79
+ return nil
80
+ end
81
+
82
+ ##################################################################################
83
+ # Adds a value(s) to the {#current_filter}. The filter stack is implemented as an Array of Hashes.
84
+ # The {#current_filter} will always be a Hash of key/value pairs. Each key represents a type of filter:
85
+ #
86
+ # The filter types are:
87
+ # - :actions
88
+ # - :verbs
89
+ # - :names
90
+ # - controllers
91
+ #
92
+ # Each key has an associated Array of Strings or Symbols. The default filter is:
93
+ #
94
+ # {actions: [:index, :show], verbs: [:get], names: [], controllers: []}
95
+ #
96
+ # The default is to include all routes that have an :action of :index or :show or include routes that have
97
+ # a verb of :get. Basically, including urls that a search engine would crawl.
98
+ # The Array of values added to the filter can be mixed. However, the convention is to use Symbols for
99
+ # :actions and :verbs and use Strings for :names and :controllers. Values are automagically converted to Symbols
100
+ # if the key is :actions or :verbs.
101
+ #
102
+ # include_filter(:actions, :edit) # => {actions: [:index, :show, :edit], verbs: [:get], names: [], controllers: []}
103
+ # include_filter(:actions, [:edit, :update]) # => {actions: [:index, :show, :edit, :update], verbs: [:get], names: [], controllers: []}
104
+ # include_filter(:actions, "edit") # => {actions: [:index, :show, :edit], verbs: [:get], names: [], controllers: []}
105
+ # include_filter(:actions, ["edit", "update"]) # => {actions: [:index, :show, :edit], verbs: [:get], names: [], controllers: []}
106
+ # include_filter(:actions, [:edit, "update"]) # => {actions: [:index, :show, :edit], verbs: [:get], names: [], controllers: []}
107
+ # include_filter(:verbs, :post) # => {actions: [:index, :show], verbs: [:get, :post], names: [], controllers: []}
108
+ #
109
+ # @overload include_filter(key, value)
110
+ # @param [Symbol, String] key The type of filter to update. :actions, :verbs, :names, :controllers.
111
+ # @param [String, Symbol, Array] value A single or Array of items to be added to the filter section specified via key.
112
+ # @return [NilClass]
113
+ def include_filter(*args)
114
+ args.insert(0, :include)
115
+ return update_filter(*args)
116
+ end
117
+
118
+ ##################################################################################
119
+ # Adds or removes value(s) to or from the {#current_filter}. This method is called by {#include_filter} and {#exclude_filter}.
120
+ # @overload update_filter(action, key, value)
121
+ # @param [Symbol, String] action The action to perform: :include or :exclude.
122
+ # @param [Symbol, String] key The type of filter to update. :actions, :verbs, :names, :controllers.
123
+ # @param [String, Symbol, Array] value A single or Array of items to be added to the filter section specified via key.
124
+ # @return [NilClass]
125
+ def update_filter(*args)
126
+
127
+ action = args.shift
128
+ action = action.kind_of?(Symbol) ? action : action.to_sym
129
+
130
+ key = args.shift
131
+ key = key.kind_of?(Symbol) ? key : key.to_sym
132
+
133
+ # list will always be concatenated to the target array
134
+ list = []
135
+
136
+ # build the list array depending on what the user passed to the method
137
+ args.each do |item|
138
+ if item.kind_of?(Array) && item.any?
139
+ list.concat(item)
140
+ else
141
+ list.push(item)
142
+ end
143
+ end
144
+
145
+ # convert all of the items in the list to symbols
146
+ # for :actions or :verbs
147
+ if key.eql?(:actions) || key.eql?(:verbs)
148
+ list.each_with_index do |item, index|
149
+ unless item.kind_of?(Symbol)
150
+ list[index] = item.to_sym
151
+ end
152
+ end
153
+ end
154
+
155
+ if action == :include
156
+
157
+ # now, simply concatenate the resulting list and make sure the final array is unique
158
+ self.current_filter[key].concat(list)
159
+ self.current_filter[key].uniq!
160
+
161
+ elsif action == :exclude
162
+
163
+ self.current_filter[key].reject! {|item| list.include?(item)}
164
+
165
+ end
166
+
167
+ return nil
168
+ end
169
+
170
+ ##################################################################################
171
+ # Removes value(s) from the {#current_filter}. Basically, the opposite of {#include_filter}.
172
+ # @overload exclude_filter(key, value)
173
+ # @param [Symbol, String] key The type of filter to update. :actions, :verbs, :names, :controllers.
174
+ # @param [String, Symbol, Array] value A single or Array of items to be added to the filter section specified via key.
175
+ # @return [NilClass]
176
+ def exclude_filter(*args)
177
+ args.insert(0, :exclude)
178
+ return update_filter(*args)
179
+ end
180
+
181
+ ##################################################################################
182
+ # Clears all types (:actions, :verbs, :names, :controllers) for the {#current_filter}.
183
+ # @return [Nil]
184
+ def clear_filters
185
+ self.current_filter = {actions: [], verbs: [], names: [], controllers: []}
186
+ return nil
187
+ end
188
+
189
+ ##################################################################################
190
+ # Clears a single type of filter.
191
+ # @param [Symbol, String] key The type of filter to update. :actions, :verbs, :names, :controllers.
192
+ # @return [Nil]
193
+ def clear_filter(key)
194
+ key = key.kind_of?(Symbol) ? key : key.to_sym
195
+ self.current_filter[key] = []
196
+ return nil
197
+ end
198
+
199
+ end
200
+ end
@@ -0,0 +1,102 @@
1
+ require 'active_support/concern'
2
+
3
+ module DuckMap
4
+ module Handlers
5
+
6
+ autoload :Index, "duck_map/handlers/index"
7
+ autoload :Show, "duck_map/handlers/show"
8
+
9
+ ##################################################################################
10
+ # Base module containing common code for all sitemap handler modules.
11
+ module Base
12
+ extend ActiveSupport::Concern
13
+
14
+ ##################################################################################
15
+ # Finds the first instance of a model object on the current controller object.
16
+ # sitemap_first_model will respect the current setting of :first_model. If :first_model
17
+ # is true, then, it will continue the search and return the first instance of a model object
18
+ # on the current controller object. Otherwise, it will return nil.
19
+ #
20
+ # @param [Hash] handler The handler Hash of a sitemap configuration.
21
+ # @return [ActiveRecord::Base]
22
+ def sitemap_first_model(handler = {})
23
+ value = nil
24
+
25
+ begin
26
+
27
+ # determine if we are suppose to look for the first model on the current controller.
28
+ if handler.kind_of?(Hash) && handler[:first_model]
29
+
30
+ first_model_object = self.find_first_model_object
31
+
32
+ unless first_model_object.blank?
33
+ value = first_model_object
34
+ end
35
+
36
+ end
37
+
38
+ rescue Exception => e
39
+ # expect this exception to occur EVERYTIME...
40
+ end
41
+
42
+ return value
43
+ end
44
+
45
+ ##################################################################################
46
+ # Returns a copy of the current values held by {DuckMap::Config.attributes}
47
+ # @return [Hash]
48
+ def sitemap_defaults(options = {})
49
+ return DuckMap::Config.copy_attributes
50
+ end
51
+
52
+ ##################################################################################
53
+ # Applies the current url limit to a result set. This is really a helper method used
54
+ # by all of the handler methods that return a Array of Hashes numbering greater than one.
55
+ # Handler methods that would normally return one row need not use this helper method.
56
+ # Use it if you implement a custom handler method.
57
+ # @param [Array] rows An Array of Hashes representing the contents of a sitemap.
58
+ # @param [Hash] options The :handler section of a sitemap configuration.
59
+ # @option options [Symbol] :url_limit An integer limiting the number of rows to return.
60
+ # @return [Array]
61
+ def sitemap_url_limit(rows, options = {})
62
+
63
+ unless options[:url_limit].blank?
64
+ if rows.length > options[:url_limit]
65
+ rows.slice!(options[:url_limit], rows.length)
66
+ end
67
+ end
68
+
69
+ return rows
70
+ end
71
+
72
+ ##################################################################################
73
+ # @return [Hash]
74
+ def sitemap_url_options(options = {})
75
+ values = {}
76
+
77
+ unless options[:url_format].blank?
78
+ values[:format] = options[:url_format]
79
+ end
80
+
81
+ return values.merge(sitemap_host_options(options))
82
+ end
83
+
84
+ ##################################################################################
85
+ # @return [Hash]
86
+ def sitemap_host_options(options = {})
87
+ values = {}
88
+
89
+ unless options[:canonical_host].blank?
90
+ values[:host] = options[:canonical_host]
91
+ end
92
+ unless options[:canonical_port].blank?
93
+ values[:port] = options[:canonical_port]
94
+ end
95
+
96
+ return values
97
+ end
98
+
99
+ end
100
+
101
+ end
102
+ end