excalibur 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3a2f5e3f336d2c2b9cd05b21f50387fca47779dd
4
+ data.tar.gz: ef5f966e21a9f27b7c23db57533d5c6f6dece688
5
+ SHA512:
6
+ metadata.gz: 51506887560a0d4771933ef36713ca814c96ed4c2c12fc2ba31cdafd214a7b982c15d0df47a9d33c70bef15e5f03bc50bd5f6086bc55aa1439b5b3a803eda9c3
7
+ data.tar.gz: eefbce53136c980ee7be535160ed801efc794052bb84686bfcde9152bc35319af85034fc059b19cba6ad9f2383db0d657a28cea935eea4ea6a0896cd62c3623a
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --warnings
3
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in excalibur.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Joost (yopefonic) Elfering
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,162 @@
1
+ # Excalibur
2
+
3
+ Excalibur is a SEO gem for [Ruby on Rails](rubyonrails.org) and helps you to
4
+ set the title and meta tags for you site overall and per page. Unlike other
5
+ options like [meta-tags](https://github.com/kpumuk/meta-tags) and
6
+ [meta_magic](https://github.com/lassebunk/metamagic) Excalibur focusses on
7
+ providing an object based DSL to turn the objects you are presenting on the
8
+ page into SEO related tags in the head.
9
+
10
+ ## Installation
11
+
12
+ Add this line to your application's Gemfile:
13
+
14
+ gem 'excalibur'
15
+
16
+ And then execute:
17
+
18
+ $ bundle
19
+
20
+ Or install it yourself as:
21
+
22
+ $ gem install excalibur
23
+
24
+ ### Initialize
25
+
26
+ To add an initializer to your application execute:
27
+
28
+ rails generate excalibur:install
29
+
30
+ The initializer is documented with the possible options you have to customize
31
+ your default setup of Excalibur.
32
+
33
+ ## Usage
34
+
35
+ ### Layout
36
+
37
+ In your layout you can replace your title tag with the Excalibur render title
38
+ method:
39
+
40
+ ```erb
41
+ <%= render_title_tag %>
42
+ ```
43
+
44
+ To render meta tags place this between the ```<head>``` tags of your layout:
45
+
46
+ ```erb
47
+ <%= render_meta_tags %>
48
+ ```
49
+
50
+ ### Views
51
+
52
+ As excalibur believes in separation of concerns passing the current object you
53
+ want to render title and meta tags for is done in the view.
54
+
55
+ ```erb
56
+ <% entitle @your_object %>
57
+ ```
58
+
59
+ The object your passing into the entitle method will search for a Excalibur
60
+ decorator. You can find out more on how to create them in the
61
+ [decorators](#decorators) section.
62
+
63
+ To modify the configuration in the view you can pass an option into the
64
+ ```entitle``` method and pass it a new excalibur config object. Read more
65
+ about creating your own in the [configurations](#configurations) section. It
66
+ will try and merge the supplied configuration with the one specified in the
67
+ decorator. Because of this you need to supply it with the same type of object
68
+ to make that possible.
69
+
70
+ Notably the second argument of ```entitle``` can also pass other
71
+ [Draper](https://github.com/drapergem/draper) related options like draper's
72
+ context.
73
+
74
+ ```ruby
75
+ entitle @your_object, config: @custom_config
76
+ ```
77
+
78
+ ### Decorators
79
+
80
+ The decorators are what turns your object into data that can be used for the
81
+ meta tags and the title. So without a decorator for an object the view helper
82
+ ``` entitle ``` will have no use at all.
83
+
84
+ To scaffold a decorator execute:
85
+
86
+ rails generate excalibur:install [class name]
87
+
88
+ A detailed description on how the decorators work can be found in the newly
89
+ created decorator class. As a side note the ```Excalibur::Decorator``` is a
90
+ subclass of ```Draper::Decorator``` and comes with the free functionality
91
+ provided by [Draper](https://github.com/drapergem/draper). Have fun!
92
+
93
+ ### Configurations
94
+
95
+ **WARNING:** handle with care!
96
+
97
+ So you want to roll your own configuration, awesome!
98
+ ```Excalibur::Configuration``` can be initialized with 3 arguments that are
99
+ the title, description and meta tags.
100
+
101
+ Both title and description are an instance of
102
+ ```Excalibur::TruncateableContent``` but as long as you make sure a
103
+ ```.to_s``` with one argument is present for the object you put in there it is
104
+ fine. The object passed to it is either a decorated object when it is set or a
105
+ blank decorated object.
106
+
107
+ ```Excalibur::TruncateableContent``` can be of course used on it's own when
108
+ creating a new configuration. It also takes 3 arguments. Two hashes; the first
109
+ for the content and the second for options. On it's own the
110
+ TruncateableContent does not do a lot but the third argument takes a
111
+ ```Proc``` that is called with the decorator on render. By default the proc
112
+ combines the content according to the content and the options supplied in the
113
+ first two hashes. But of course you can supply your own. Setting them to empty
114
+ hashes or nil will work and when merged the default will keep in place.
115
+
116
+ The meta tags, and third argument of the configuration is a structured hash
117
+ with two layers. The first layer is the global type of meta tag and the second
118
+ contains the sub-types with the content.
119
+
120
+ so a meta tag like this:
121
+
122
+ ```html
123
+ <meta name="description" content="Excalibur is cool" />
124
+ ```
125
+
126
+ you need a hash like this:
127
+
128
+ ```ruby
129
+ {
130
+ name: {
131
+ description: 'Excalibur is cool'
132
+ }
133
+ }
134
+ ```
135
+
136
+ For an OpenGraph tag this would be something like:
137
+
138
+ ```html
139
+ <meta property="og:description" content="Excalibur is cool" />
140
+ ```
141
+
142
+ ```ruby
143
+ {
144
+ property: {
145
+ 'og:description' => 'Excalibur is cool'
146
+ }
147
+ }
148
+ ```
149
+
150
+ The content of the meta_tags can be defined as an Array of items or just a
151
+ single one. The item itself can be any type of variable that render as a
152
+ string on a page. It can also be a ```Proc``` that will be passed the
153
+ decorated object just like the title and description. This Proc can also
154
+ result in an array.
155
+
156
+ ## Contributing
157
+
158
+ 1. Fork it ( https://github.com/yopefonic/excalibur/fork )
159
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
160
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
161
+ 4. Push to the branch (`git push origin my-new-feature`)
162
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
data/excalibur.gemspec ADDED
@@ -0,0 +1,32 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'excalibur/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'excalibur'
8
+ spec.version = Excalibur::VERSION
9
+ spec.authors = ['Joost Elfering']
10
+ spec.email = ['yopefonic@gmail.com']
11
+ spec.summary = %q{helper gem to set page title and meta tags in a rails app}
12
+ spec.description = %q{helper gem for rails apps to set the title and meta tags for a page. The helper can take custom input per page, objects that use decorator to determine the tags or an application wide configurable default.}
13
+ spec.homepage = 'https://github.com/yopefonic/excalibur'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.required_ruby_version = '>= 1.9.3'
22
+
23
+ spec.add_dependency 'rails', '~> 3'
24
+ spec.add_dependency 'draper', '~> 1.3', '>= 1.3.0'
25
+
26
+ spec.add_development_dependency 'bundler', '~> 1.6'
27
+ spec.add_development_dependency 'rake', '~> 10.3'
28
+ spec.add_development_dependency 'rspec', '~> 3.0'
29
+ spec.add_development_dependency 'generator_spec', '~> 0.9', '>= 0.9.2'
30
+ spec.add_development_dependency 'simplecov', '~> 0.8', '>= 0.8.0'
31
+ spec.add_development_dependency 'pry', '~> 0.10', '>= 0.10.1'
32
+ end
@@ -0,0 +1,77 @@
1
+ module Excalibur
2
+ # the Configuration class is responsible for holding the configurable data
3
+ # and logic that will be passed down
4
+ class Configuration
5
+ include Duplicator
6
+
7
+ attr_accessor :title
8
+ attr_accessor :description
9
+ attr_accessor :meta_tags
10
+
11
+ def initialize(
12
+ title = TruncateableContent.new,
13
+ description = TruncateableContent.new,
14
+ meta_tags = ::HashWithIndifferentAccess.new({}))
15
+ @title = title
16
+ @description = description
17
+ @meta_tags = meta_tags
18
+ end
19
+
20
+ def merge!(obj)
21
+ if obj.is_a? Configuration
22
+ @title = merge_instance(@title, obj.title)
23
+ @description = merge_instance(@description, obj.description)
24
+ @meta_tags = merge_instance(@meta_tags, obj.meta_tags)
25
+
26
+ self
27
+ else
28
+ fail(TypeError.new(true),
29
+ 'can only merge two Excalibur::Configuration objects')
30
+ end
31
+ end
32
+
33
+ def dup
34
+ self.class.new(
35
+ dup_instance(@title),
36
+ dup_instance(@description.dup),
37
+ dup_instance(@meta_tags.dup))
38
+ end
39
+
40
+ def set_meta_tag(type, name, value = nil)
41
+ if @meta_tags[type].nil?
42
+ @meta_tags[type] = ::HashWithIndifferentAccess.new
43
+ end
44
+
45
+ @meta_tags[type][name] = value
46
+ end
47
+
48
+ def remove_meta_tag(type, name)
49
+ @meta_tags[type].delete(name) if @meta_tags[type].present?
50
+ @meta_tags.delete(type) if @meta_tags[type].empty?
51
+ end
52
+
53
+ private
54
+
55
+ def merge_instance(old, new)
56
+ if old.is_a? new.class
57
+ merge_content(old, new)
58
+ elsif !new.nil?
59
+ new
60
+ else
61
+ old
62
+ end
63
+ end
64
+
65
+ def merge_content(old, new)
66
+ if old.is_a? ::Hash
67
+ old.deep_merge!(new)
68
+ elsif old.is_a? TruncateableContent
69
+ old.merge!(new)
70
+ elsif old.is_a? ::String
71
+ old + new
72
+ else
73
+ new
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,55 @@
1
+ require 'draper'
2
+
3
+ module Excalibur
4
+ # the Decorator class helps content to be derived from application base
5
+ # object and turn them into Excalibur titles and meta tags. It's main
6
+ # responsibilities are to make local classes configurable and it acts as a
7
+ # connector between the application's objects and Excalibur.
8
+ class Decorator < ::Draper::Decorator
9
+ delegate_all
10
+
11
+ attr_accessor :custom_configuration
12
+
13
+ class << self
14
+ attr_writer :configuration
15
+
16
+ def excalibur_init(config = configuration)
17
+ @configuration = config
18
+ end
19
+
20
+ def configuration
21
+ @configuration ||= ::Excalibur.configuration.dup
22
+ end
23
+
24
+ def method_missing(meth, *args)
25
+ if meth.to_s =~ /^excalibur_set_(title|description+)_(content|option|combinator+)$/
26
+ configuration.send($1).send("update_#{$2}", *args)
27
+ else
28
+ super
29
+ end
30
+ end
31
+
32
+ def excalibur_set_meta_tag(type, name, value = nil)
33
+ configuration.set_meta_tag(type, name, value)
34
+ end
35
+ end
36
+
37
+ def initialize(object, options = {})
38
+ configuration.merge!(options.delete(:config)) if options.key?(:config)
39
+
40
+ super(object, options)
41
+ end
42
+
43
+ def configuration
44
+ @custom_configuration ||= self.class.configuration.dup
45
+ end
46
+
47
+ def customize_configuration(config)
48
+ configuration.merge!(config)
49
+ end
50
+
51
+ def render_title(obj = self)
52
+ configuration.title.to_s(obj) if configuration.title.present?
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,19 @@
1
+ module Excalibur
2
+ # the Duplicator module helps in making sure duplication of nested objects
3
+ # functions properly and is used by the class Configuration and the class
4
+ # TruncableContent
5
+ module Duplicator
6
+ # duplicates TruncatableContent and calls the deep_dup method on any form
7
+ # of Hash. Otherwise return the object as not all objects do not need to
8
+ # be duplicated.
9
+ def dup_instance(obj)
10
+ if obj.is_a?(TruncateableContent)
11
+ obj.dup
12
+ elsif obj.is_a?(Hash)
13
+ obj.deep_dup
14
+ else
15
+ obj
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,15 @@
1
+ require 'excalibur/view_helpers'
2
+
3
+ module Excalibur
4
+ # ties the ViewHelpers into Rails and allows them to be used in the views
5
+ class Railtie < Rails::Railtie
6
+ initializer 'excalibur.view_helpers' do
7
+ ActionView::Base.send :include, ViewHelpers
8
+ end
9
+
10
+ generators do
11
+ require 'generators/excalibur/install_generator'
12
+ require 'generators/excalibur/decorator_generator'
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,82 @@
1
+ module Excalibur
2
+ # The TruncateableContent class is responsible for text content that is
3
+ # constrained in length due to SEO specifications. Mainly used for title
4
+ # and meta description tags this class facilitates the creation and proper
5
+ # rendering of the content. A prefix/body/suffix data structure is used so
6
+ # that only the body is truncated without having an effect on the branding
7
+ # that is put before and/or after the body.
8
+ class TruncateableContent
9
+ include Duplicator
10
+
11
+ attr_accessor :content
12
+ attr_accessor :options
13
+ attr_accessor :combinator
14
+
15
+ def initialize(content = {}, options = {}, combinator = nil)
16
+ @content = ::HashWithIndifferentAccess.new(content)
17
+ @options = ::HashWithIndifferentAccess.new(options)
18
+ @combinator = combinator
19
+ end
20
+
21
+ def can_merge?(obj)
22
+ obj.is_a? TruncateableContent
23
+ end
24
+
25
+ def merge!(obj)
26
+ if can_merge?(obj)
27
+ @content.merge!(obj.content)
28
+ @options.merge!(obj.options)
29
+ @combinator = obj.combinator unless obj.combinator.nil?
30
+
31
+ self
32
+ else
33
+ fail(TypeError.new(true),
34
+ 'can only merge two Excalibur::TruncateableContent objects')
35
+ end
36
+ end
37
+
38
+ def dup
39
+ self.class.new(
40
+ dup_instance(@content),
41
+ dup_instance(@options),
42
+ dup_instance(@combinator)
43
+ )
44
+ end
45
+
46
+ def get_content(key, obj = nil)
47
+ if @content[key].instance_of?(Proc)
48
+ @content[key].call(obj)
49
+ elsif @content[key].nil?
50
+ ''
51
+ else
52
+ @content[key]
53
+ end
54
+ end
55
+
56
+ def update_content(key, value = nil)
57
+ @content[key] = value
58
+ end
59
+
60
+ def update_option(key, value = nil)
61
+ @options[key] = value
62
+ end
63
+
64
+ def update_combinator(value = nil)
65
+ @combinator = value
66
+ end
67
+
68
+ def render_long(obj = nil)
69
+ @content.map { |key, _value| get_content(key, obj).to_s }.inject(:+).to_s
70
+ end
71
+
72
+ def render_short(obj = nil)
73
+ if @combinator.instance_of? Proc
74
+ @combinator.call(obj)
75
+ else
76
+ render_long(obj)
77
+ end
78
+ end
79
+
80
+ alias_method :to_s, :render_short
81
+ end
82
+ end
@@ -0,0 +1,4 @@
1
+ # setting version number for the Excalibur gem
2
+ module Excalibur
3
+ VERSION = '0.0.2'
4
+ end
@@ -0,0 +1,59 @@
1
+ module Excalibur
2
+ # the ViewHelpers module contains the methods to access Excalibur from the
3
+ # view. Most commonly placed in the view templates and the application Layout
4
+ module ViewHelpers
5
+ def entitle(object, options = {})
6
+ @excalibur_subject = excalibur_decorate_subject(object, options)
7
+ end
8
+
9
+ def render_title_tag
10
+ content_tag :title, excalibur_subject.render_title
11
+ end
12
+
13
+ def render_meta_tags
14
+ result = ''
15
+
16
+ excalibur_subject.configuration.meta_tags.each do |type_name, value|
17
+ value.each do |type_value, contents|
18
+ next if contents.nil?
19
+ contents = render_meta_content(contents)
20
+ contents = [contents] unless contents.is_a?(Array)
21
+
22
+ contents.each do |content|
23
+ result << tag(
24
+ :meta,
25
+ type_name => type_value,
26
+ content: content
27
+ )
28
+ end
29
+ end
30
+ end
31
+
32
+ result.html_safe
33
+ end
34
+
35
+ private
36
+
37
+ def render_meta_content(content)
38
+ if content.is_a? Proc
39
+ content.call(excalibur_subject)
40
+ else
41
+ content
42
+ end
43
+ end
44
+
45
+ def excalibur_subject
46
+ @excalibur_subject ||= new_blank_excalibur_subject
47
+ end
48
+
49
+ def new_blank_excalibur_subject
50
+ ::Excalibur::Decorator.decorate(true)
51
+ end
52
+
53
+ def excalibur_decorate_subject(object, options = {})
54
+ Object.const_get(
55
+ "::Excalibur::#{object.class.name}Decorator"
56
+ ).decorate(object, options)
57
+ end
58
+ end
59
+ end
data/lib/excalibur.rb ADDED
@@ -0,0 +1,91 @@
1
+ require 'excalibur/version'
2
+ require 'active_support/all'
3
+ require 'excalibur/decorator'
4
+ require 'excalibur/duplicator'
5
+ require 'excalibur/configuration'
6
+ require 'excalibur/truncatable_content'
7
+ require 'excalibur/railtie' if defined?(Rails)
8
+
9
+ # the Excalibur gem helps you to decorate a rails app with proper title and
10
+ # meta tags for every page. It does this by providing default configuration
11
+ # options, object based decorators for customization per object type and
12
+ # helpers to put in on the page.
13
+ module Excalibur
14
+ class << self
15
+ attr_writer :configuration
16
+
17
+ def configuration
18
+ @configuration ||= new_default_configuration
19
+ end
20
+
21
+ def reset
22
+ @configuration = new_default_configuration
23
+ end
24
+
25
+ # defines the gem default
26
+ def new_default_configuration
27
+ Configuration.new(
28
+ new_default_title,
29
+ new_default_description,
30
+ new_default_meta_tags
31
+ )
32
+ end
33
+
34
+ def configure
35
+ yield(configuration)
36
+ end
37
+
38
+ private
39
+
40
+ def new_default_title
41
+ TruncateableContent.new(
42
+ { body: 'Excalibur' },
43
+ { length: 69, omission: '...', separator: '' },
44
+ proc do |obj|
45
+ if obj.present?
46
+ t = obj.configuration.title
47
+
48
+ length = t.options[:length]
49
+ length -= t.get_content(:prefix, obj).length
50
+ length -= t.get_content(:suffix, obj).length
51
+
52
+ res = t.get_content(:prefix, obj)
53
+ res += t.get_content(:body, obj).truncate(length, t.options)
54
+ res + t.get_content(:suffix, obj)
55
+ end
56
+ end
57
+ )
58
+ end
59
+
60
+ def new_default_description
61
+ TruncateableContent.new(
62
+ { body: 'Excalibur; a worthy title for a gem about titles.' },
63
+ { length: 155, omission: '...', separator: ' ' },
64
+ proc do |obj|
65
+ if obj.present?
66
+ d = obj.configuration.description
67
+
68
+ length = d.options[:length]
69
+ length -= d.get_content(:prefix, obj).length
70
+ length -= d.get_content(:suffix, obj).length
71
+
72
+ res = d.get_content(:prefix, obj)
73
+ res += d.get_content(:body, obj).truncate(length, d.options)
74
+ res + d.get_content(:suffix, obj)
75
+ end
76
+ end
77
+ )
78
+ end
79
+
80
+ def new_default_meta_tags
81
+ ::HashWithIndifferentAccess.new(
82
+ name: ::HashWithIndifferentAccess.new(
83
+ description: proc do |obj|
84
+ obj.configuration.description.to_s(obj)
85
+ end,
86
+ viewport: 'width=device-width, initial-scale=1'
87
+ )
88
+ )
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,18 @@
1
+ require 'rails/generators/base'
2
+
3
+ module Excalibur
4
+ module Generators
5
+ # allows you to use rails generate to create a new scaffolded decorator
6
+ # for a application native class.
7
+ # rails g excalibur:decorator [class_name]
8
+ class DecoratorGenerator < Rails::Generators::NamedBase
9
+ source_root File.expand_path('../../templates', __FILE__)
10
+
11
+ desc 'Creates a Excalibur decorator.'
12
+
13
+ def create_decorator
14
+ template 'decorator.rb', "app/decorators/excalibur/#{file_name}_decorator.rb"
15
+ end
16
+ end
17
+ end
18
+ end