para-seo_tools 0.2.1

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.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +178 -0
  6. data/Rakefile +2 -0
  7. data/app/.DS_Store +0 -0
  8. data/app/components/seo_tools_skeleton_component.rb +7 -0
  9. data/app/controllers/.DS_Store +0 -0
  10. data/app/controllers/admin/para/seo_tools/skeleton_resources_controller.rb +21 -0
  11. data/app/decorators/seo_tools_skeleton_component_decorator.rb +3 -0
  12. data/app/helpers/para/seo_tools/admin/fields_helper.rb +15 -0
  13. data/app/models/para/seo_tools/page.rb +54 -0
  14. data/app/views/.DS_Store +0 -0
  15. data/app/views/admin/.DS_Store +0 -0
  16. data/app/views/admin/para/seo_tools/pages/_actions.html.haml +5 -0
  17. data/app/views/admin/para/seo_tools/pages/_filters.html.haml +25 -0
  18. data/app/views/admin/para/seo_tools/pages/_form.html.haml +13 -0
  19. data/app/views/admin/para/seo_tools/pages/_table.html.haml +20 -0
  20. data/app/views/admin/para/seo_tools/skeleton_resources/index.html.haml +4 -0
  21. data/bin/rails +12 -0
  22. data/config/locales/para-seo_tools.fr.yml +22 -0
  23. data/db/migrate/20150601085253_create_seo_tools_pages.rb +14 -0
  24. data/db/migrate/20160610094032_add_meta_tags_to_seo_tools_pages.rb +12 -0
  25. data/db/migrate/20160614081242_add_locale_and_remove_meta_tags_list_to_seo_tools_pages.rb +8 -0
  26. data/lib/generators/para/seo_tools/install/install_generator.rb +24 -0
  27. data/lib/generators/para/seo_tools/install/templates/initializer.rb +33 -0
  28. data/lib/generators/para/seo_tools/install/templates/skeleton.rb +37 -0
  29. data/lib/para/seo_tools/controller.rb +50 -0
  30. data/lib/para/seo_tools/engine.rb +27 -0
  31. data/lib/para/seo_tools/meta_tags/renderer.rb +80 -0
  32. data/lib/para/seo_tools/meta_tags/store.rb +52 -0
  33. data/lib/para/seo_tools/meta_tags/tags/base.rb +49 -0
  34. data/lib/para/seo_tools/meta_tags/tags/description.rb +54 -0
  35. data/lib/para/seo_tools/meta_tags/tags/image.rb +46 -0
  36. data/lib/para/seo_tools/meta_tags/tags/keywords.rb +20 -0
  37. data/lib/para/seo_tools/meta_tags/tags/title.rb +48 -0
  38. data/lib/para/seo_tools/meta_tags/tags/url.rb +13 -0
  39. data/lib/para/seo_tools/meta_tags/tags.rb +29 -0
  40. data/lib/para/seo_tools/meta_tags/vendors/base.rb +27 -0
  41. data/lib/para/seo_tools/meta_tags/vendors/open_graph.rb +23 -0
  42. data/lib/para/seo_tools/meta_tags/vendors/twitter.rb +23 -0
  43. data/lib/para/seo_tools/meta_tags/vendors.rb +17 -0
  44. data/lib/para/seo_tools/meta_tags.rb +12 -0
  45. data/lib/para/seo_tools/routes.rb +15 -0
  46. data/lib/para/seo_tools/sitemap.rb +13 -0
  47. data/lib/para/seo_tools/skeleton/page.rb +71 -0
  48. data/lib/para/seo_tools/skeleton/site.rb +18 -0
  49. data/lib/para/seo_tools/skeleton/worker.rb +11 -0
  50. data/lib/para/seo_tools/skeleton.rb +79 -0
  51. data/lib/para/seo_tools/version.rb +5 -0
  52. data/lib/para/seo_tools/view_helpers.rb +9 -0
  53. data/lib/para/seo_tools.rb +43 -0
  54. data/lib/tasks/migration.rake +20 -0
  55. data/lib/tasks/sitemap.rake +17 -0
  56. data/lib/tasks/skeleton.rake +9 -0
  57. data/para-seo_tools.gemspec +27 -0
  58. data/test/fixtures/seo_tools/pages.yml +11 -0
  59. data/test/models/seo_tools/page_test.rb +7 -0
  60. metadata +181 -0
@@ -0,0 +1,80 @@
1
+ module Para
2
+ module SeoTools
3
+ module MetaTags
4
+ class Renderer
5
+ LINE_SEPARATOR = "\n"
6
+
7
+ attr_reader :template, :vendors
8
+
9
+ delegate :content_tag, :tag, to: :template
10
+
11
+ def initialize(template, vendors: nil)
12
+ @template = template
13
+ @vendors = vendors
14
+ end
15
+
16
+ def render
17
+ [
18
+ charset_tag,
19
+ title_tag,
20
+ description_tag,
21
+ keywords_tag,
22
+ canonical_tag,
23
+ vendor_tags
24
+ ].compact.join(LINE_SEPARATOR).html_safe
25
+ end
26
+
27
+ private
28
+
29
+ def title_tag
30
+ if (title = meta_tags.title).present?
31
+ content_tag(:title, title)
32
+ end
33
+ end
34
+
35
+ def charset_tag
36
+ tag(:meta, charset: meta_tags.charset)
37
+ end
38
+
39
+ def description_tag
40
+ if (description = meta_tags.description).present?
41
+ tag(:meta, name: 'description', content: description)
42
+ end
43
+ end
44
+
45
+ def keywords_tag
46
+ if (keywords = meta_tags.keywords).present?
47
+ tag(:meta, name: 'keywords', content: keywords)
48
+ end
49
+ end
50
+
51
+ def canonical_tag
52
+ if (canonical = meta_tags.canonical).present?
53
+ tag(:link, rel: 'canonical', href: canonical)
54
+ end
55
+ end
56
+
57
+ def vendor_tags
58
+ return unless vendors
59
+
60
+ vendor_tags = vendors.each_with_object([]) do |vendor_name, tags|
61
+ vendor_class = MetaTags::Vendors.for(vendor_name)
62
+ vendor = vendor_class.new(template)
63
+
64
+ vendor.tags.each do |tag_name|
65
+ if (value = meta_tags.send(tag_name)).present?
66
+ tags << vendor.render(tag_name, value)
67
+ end
68
+ end
69
+ end
70
+
71
+ vendor_tags.join(LINE_SEPARATOR)
72
+ end
73
+
74
+ def meta_tags
75
+ @meta_tags ||= template.controller.meta_tags_store
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,52 @@
1
+ module Para
2
+ module SeoTools
3
+ module MetaTags
4
+ class Store
5
+ attr_reader :controller, :defaults
6
+
7
+ TAGS = :title, :description, :image, :url, :site_name, :keywords, :type,
8
+ :site, :card, :charset, :canonical
9
+
10
+ attr_writer *TAGS
11
+
12
+ def initialize(controller)
13
+ @controller = controller
14
+
15
+ @defaults = if Para::SeoTools.defaults
16
+ controller.instance_exec(&Para::SeoTools.defaults)
17
+ else
18
+ {}
19
+ end
20
+ end
21
+
22
+ def charset
23
+ @charset ||= 'utf-8'
24
+ end
25
+
26
+ TAGS.each do |tag_name|
27
+ define_method(tag_name) do
28
+ fetch_value_for(tag_name)
29
+ end unless method_defined?(tag_name)
30
+ end
31
+
32
+ private
33
+
34
+ def fetch_value_for(tag_name)
35
+ ivar_name = :"@#{ tag_name }"
36
+
37
+ if (value = instance_variable_get(ivar_name)).present?
38
+ value
39
+ else
40
+ value = if (processor = MetaTags::Tags[tag_name])
41
+ processor.new(controller).value
42
+ end
43
+
44
+ value = defaults[tag_name] unless value.present?
45
+
46
+ instance_variable_set(ivar_name, value) if value
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,49 @@
1
+ module Para
2
+ module SeoTools
3
+ module MetaTags
4
+ module Tags
5
+ class Base
6
+ attr_accessor :controller
7
+
8
+ def initialize(controller)
9
+ @controller = controller
10
+ end
11
+
12
+ # Overriding this method in subclasses allow to process the meta tag
13
+ # value before it is rendered, e.g. stripping HTML tags
14
+ #
15
+ def self.process(value)
16
+ value
17
+ end
18
+
19
+ protected
20
+
21
+ def member_action?
22
+ controller.params.key?(:id)
23
+ end
24
+
25
+ def action_i18n(label)
26
+ I18n.t(action_key(label), default: '').presence
27
+ end
28
+
29
+ def action_key(label)
30
+ key = %w(meta_tags controller)
31
+ key << controller.params[:controller]
32
+ key << controller.params[:action]
33
+ key << label
34
+ key.join(".")
35
+ end
36
+
37
+ # TODO : Define if we'll still support instance meta tags and refactor
38
+ # accordingly
39
+ def instance
40
+ end
41
+
42
+ def model_name
43
+ @model_name ||= controller.class.name.gsub(/Controller$/, '').singularize.underscore
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,54 @@
1
+ module Para
2
+ module SeoTools
3
+ module MetaTags
4
+ module Tags
5
+ class Description < Base
6
+ def value
7
+ self.class.process(
8
+ meta_taggable_description || instance_description || action_name
9
+ )
10
+ end
11
+
12
+ def self.process(value)
13
+ Processor.new.process(value)
14
+ end
15
+
16
+ private
17
+
18
+ def meta_taggable_description
19
+ instance && instance.meta_tagged? &&
20
+ instance.meta_tags_list.meta_description.presence
21
+ end
22
+
23
+ def instance_description
24
+ if instance
25
+ Para::SeoTools.description_methods.each do |method|
26
+ if instance.respond_to?(method)
27
+ if (description = instance.send(method).presence)
28
+ return description
29
+ end
30
+ end
31
+ end
32
+
33
+ return nil
34
+ end
35
+ end
36
+
37
+ def action_name
38
+ if (action_name = action_i18n(:description))
39
+ return action_name
40
+ end
41
+ end
42
+
43
+ class Processor
44
+ include ActionView::Helpers::TextHelper
45
+
46
+ def process(value)
47
+ truncate(sanitize(value, tags: []), length: 250)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,46 @@
1
+ module Para
2
+ module SeoTools
3
+ module MetaTags
4
+ module Tags
5
+ class Image < Base
6
+ def value
7
+ self.class.process(instance_image)
8
+ end
9
+
10
+ def self.process(value)
11
+ url = url_from(value)
12
+
13
+ if url.start_with?('http://')
14
+ url
15
+ else
16
+ request = RequestStore.store[:'para.seo_tools.request']
17
+ URI.join(request.url, url).to_s
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def instance_image
24
+ if member_action?
25
+ Para::SeoTools.image_methods.each do |method|
26
+ if instance.respond_to?(method) && instance.send(method)
27
+ return instance.send(method).url
28
+ end
29
+ end
30
+
31
+ nil
32
+ end
33
+ end
34
+
35
+ def self.url_from(value)
36
+ if Paperclip::Attachment === value
37
+ value.url(:thumb)
38
+ else
39
+ value.to_s
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,20 @@
1
+ module Para
2
+ module SeoTools
3
+ module MetaTags
4
+ module Tags
5
+ class Keywords < Base
6
+ def value
7
+ meta_taggable_keywords
8
+ end
9
+
10
+ private
11
+
12
+ def meta_taggable_keywords
13
+ instance && instance.meta_tagged? &&
14
+ instance.meta_tags_list.meta_keywords.presence
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,48 @@
1
+ module Para
2
+ module SeoTools
3
+ module MetaTags
4
+ module Tags
5
+ class Title < Base
6
+ def value
7
+ meta_taggable_title || instance_title || action_name || model_name_translation
8
+ end
9
+
10
+ private
11
+
12
+ def meta_taggable_title
13
+ instance && instance.meta_tagged? &&
14
+ instance.meta_tags_list.meta_title.presence
15
+ end
16
+
17
+ def instance_title
18
+ if instance
19
+ Para::SeoTools.title_methods.each do |method|
20
+ if instance.respond_to?(method) && (title = instance.send(method)).presence
21
+ return title
22
+ end
23
+ end
24
+ return nil
25
+ end
26
+ end
27
+
28
+ def action_name
29
+ if (action_name = action_i18n(:title))
30
+ return action_name
31
+ end
32
+ end
33
+
34
+ def model_name_translation
35
+ return unless model_name
36
+ model_key = "activerecord.models.#{ model_name }"
37
+
38
+ if (translation = I18n.t("#{ model_key }.other", default: "").presence)
39
+ translation
40
+ elsif (translation = I18n.t(model_key, default: "").presence)
41
+ translation.pluralize
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,13 @@
1
+ module Para
2
+ module SeoTools
3
+ module MetaTags
4
+ module Tags
5
+ class Url < Base
6
+ def value
7
+ controller.request.original_url
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,29 @@
1
+ module Para
2
+ module SeoTools
3
+ module MetaTags
4
+ module Tags
5
+ extend ActiveSupport::Autoload
6
+
7
+ autoload :Base
8
+ autoload :Title
9
+ autoload :Description
10
+ autoload :Keywords
11
+ autoload :Image
12
+ autoload :Url
13
+
14
+ def self.[](tag_name)
15
+ processors[tag_name]
16
+ end
17
+
18
+ private
19
+
20
+ def self.processors
21
+ @processors ||= [:title, :description, :keywords, :image, :url].reduce({}) do |hash, tag|
22
+ hash[tag] = MetaTags::Tags.const_get(tag.to_s.camelize)
23
+ hash
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,27 @@
1
+ module Para
2
+ module SeoTools
3
+ module MetaTags
4
+ module Vendors
5
+ class Base
6
+ include ActionView::Helpers::TagHelper
7
+
8
+ attr_reader :template
9
+
10
+ def initialize(template)
11
+ @template = template
12
+ end
13
+
14
+ def render(key, value)
15
+ tag(:meta, key_name => attribute_name_for(key), content: value)
16
+ end
17
+
18
+ protected
19
+
20
+ def attribute_name_for(key)
21
+ [namespace, key].join(':')
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,23 @@
1
+ module Para
2
+ module SeoTools
3
+ module MetaTags
4
+ module Vendors
5
+ class OpenGraph < Para::SeoTools::MetaTags::Vendors::Base
6
+ def tags
7
+ [:title, :description, :image, :type, :url, :site_name]
8
+ end
9
+
10
+ protected
11
+
12
+ def key_name
13
+ 'property'
14
+ end
15
+
16
+ def namespace
17
+ 'og'
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ module Para
2
+ module SeoTools
3
+ module MetaTags
4
+ module Vendors
5
+ class Twitter < Para::SeoTools::MetaTags::Vendors::Base
6
+ def tags
7
+ [:title, :description, :image, :url, :site, :card]
8
+ end
9
+
10
+ protected
11
+
12
+ def key_name
13
+ 'name'
14
+ end
15
+
16
+ def namespace
17
+ 'twitter'
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,17 @@
1
+ module Para
2
+ module SeoTools
3
+ module MetaTags
4
+ module Vendors
5
+ extend ActiveSupport::Autoload
6
+
7
+ autoload :Base
8
+ autoload :OpenGraph
9
+ autoload :Twitter
10
+
11
+ def self.for(vendor_name)
12
+ const_get(vendor_name.to_s.camelize)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,12 @@
1
+ module Para
2
+ module SeoTools
3
+ module MetaTags
4
+ extend ActiveSupport::Autoload
5
+
6
+ autoload :Renderer
7
+ autoload :Store
8
+ autoload :Tags
9
+ autoload :Vendors
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,15 @@
1
+ module Para
2
+ module SeoTools
3
+ class Routes < Para::Plugins::Routes
4
+ def draw
5
+ plugin :seo_tools do
6
+ crud_component :skeleton, scope: ':model' do
7
+ collection do
8
+ get :refresh
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,13 @@
1
+ module Para
2
+ module SeoTools
3
+ class Sitemap
4
+ def self.build
5
+ ::Sitemap::Generator.instance.load :host => Para::SeoTools.host do
6
+ Para::SeoTools::Skeleton.site.pages.each do |page|
7
+ literal page.path, page.sitemap_options
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,71 @@
1
+ module Para
2
+ module SeoTools
3
+ module Skeleton
4
+ class Page
5
+ include Rails.application.routes.url_helpers
6
+
7
+ attr_reader :name, :resource, :options, :locale
8
+
9
+ def initialize(name, path: nil, resource: nil, **options)
10
+ @name = name
11
+ @path = path
12
+ @resource = resource
13
+ @options = options
14
+
15
+ # Fetch locale on page build to allow calling the `page` skeleton
16
+ # method inside a `I18n.with_locale` block
17
+ #
18
+ @locale = options.fetch(:locale, I18n.locale)
19
+ end
20
+
21
+ def identifier
22
+ @identifier ||= [name, resource.try(:id)].compact.join(':')
23
+ end
24
+
25
+ def display_name
26
+ @display_name ||= [name, resource.try(:id)].compact.join(' #').humanize
27
+ end
28
+
29
+ def path
30
+ @path ||= find_route
31
+ end
32
+
33
+ def model
34
+ @model ||= self.class.model_for(identifier).tap do |page|
35
+ # Override path (i.e.: slug changed)
36
+ page.path = path if path.to_s != page.path
37
+ page.locale = locale
38
+ # Do not override meta tags if already present
39
+ page.defaults = options[:defaults] || {}
40
+ end
41
+ end
42
+
43
+ def sitemap_options
44
+ [:priority, :change_frequency].each_with_object({}) do |param, hash|
45
+ hash[param] = options[param] if options.key?(param)
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ # Find a route matching the name of the page, passing the resource
52
+ # for member routes to work out of the box
53
+ def find_route
54
+ if respond_to?(:"#{ name }_path")
55
+ send(:"#{ name }_path", resource)
56
+ end
57
+ end
58
+
59
+ def self.model_for(identifier)
60
+ models[identifier] ||= ::Para::SeoTools::Page.new(identifier: identifier)
61
+ end
62
+
63
+ def self.models
64
+ @models ||= Para::SeoTools::Page.all.each_with_object({}) do |page, hash|
65
+ hash[page.identifier] = page
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,18 @@
1
+ module Para
2
+ module SeoTools
3
+ module Skeleton
4
+ class Site
5
+ include Rails.application.routes.url_helpers
6
+
7
+ def page(name, options = {} , &block)
8
+ page = Skeleton::Page.new(name, options)
9
+ pages << page
10
+ end
11
+
12
+ def pages
13
+ @pages ||= []
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,11 @@
1
+ module Para
2
+ module SeoTools
3
+ module Skeleton
4
+ class Worker
5
+ def self.perform
6
+ Para::SeoTools::Skeleton.build
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end