para-seo_tools 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a34f864ccd5b3365f87eff521544ce23fe555c6a
4
- data.tar.gz: 98b4747257ce0acd81643e9c0b101e9706fbadb5
3
+ metadata.gz: f4b8959221c49d287170b758d7dbe9a0e2bb291b
4
+ data.tar.gz: 2c9f4d8e455c31bf0f47c7c1cdb1dffdd54a571f
5
5
  SHA512:
6
- metadata.gz: bf073b75a0b537fef122a1cec5614505b1e6e0ca0288f417a917bf68ffe015fd663de442fcde4fdda9d7de28791524e8be8acea01a5f9e0b098f318a5a9a9883
7
- data.tar.gz: 6660839f153d03a48b1957aef6dc1118e8456957d3fc00e8fb729ad015ac921074eaa7c0633fdb6bd9bdd78505e39f5994dec455033bfe38e554150aad3683f3
6
+ metadata.gz: dd129c962571f25f126aeafed737ef17e86fb2e2ef73351bf1b416a4372c4a917f80b41017a614506001a2a2fe26ba829b7108e2675df715fe99015bea4ddc73
7
+ data.tar.gz: 0880f858c0a6db534a542182e456f46f126a2ce4271bef67fc19a7e62297fb1b1330b297b4278db6f435866bc564e280f631fcd71b61816ab1278e75989b1c07
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
- source 'https://rubygems.org'
1
+ source 'http://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in seo_tools-para.gemspec
4
4
  gemspec
data/README.md CHANGED
@@ -87,6 +87,166 @@ by default.
87
87
  page :posts, priority: 1, change_frequency: 'weekly'
88
88
  ```
89
89
 
90
+ #### Default meta tag values
91
+
92
+ The `page` method allows to define default data for seo_tools to use as default
93
+ values for the title and description meta tags.
94
+
95
+ For example, using your post title as default meta title and its excerpt as the
96
+ default description meta tag value is as easy as :
97
+
98
+ ```ruby
99
+ Posts.find_each do |post|
100
+ page :post, resource: post, defaults: { title: post.title, description: post.excerpt }
101
+ end
102
+ ```
103
+
104
+ #### Arbitrary scoping of pages
105
+
106
+ SeoTools ensures your page identifiers are unique. This avoids duplicating pages
107
+ and unwanted side effects. This identifier is generated with the first argument
108
+ of the `#page` method (`:post` in our examples), and the id of the resource
109
+ passed in the `:resource` argument, when present.
110
+
111
+ For a `:post` with id `25`, this results in an identifier containing :
112
+ `"post:25"`.
113
+
114
+ Sometimes you'll need to have multiple pages that allows accessing a single
115
+ resource, and your identifier would be duplicated. In this case, you can use
116
+ the `:scope` argument, which will allow to create multiple identifiers with
117
+ the same value, scoped to the given arguments.
118
+
119
+ As an example, you can this of posts belonging to multiple categories, which
120
+ would result in a structure where a post could appear under multiple categories.
121
+
122
+ To handle this case, we would write :
123
+
124
+ ```ruby
125
+ Categories.each do |category|
126
+ category.posts.each do |post|
127
+ page :post, resource: post, path: category_post_path(category, post), scope: :category_id, category_id: post.category_id
128
+ end
129
+ end
130
+ ```
131
+
132
+ As a side effect, you can find all "sibling" pages of a given page, allowing
133
+ you to handle canonical URLs or HREFLANG meta tags with ease.
134
+
135
+ #### Batch scoping and params forwarding
136
+
137
+ When you have many pages scoped with the same parameters, you may want to DRY
138
+ out scope params passing to the `#page` method calls.
139
+
140
+ This can be accomplished with the `#with_params` helper method. When used,
141
+ every call to the `#page` method would automatically fetch params passed to the
142
+ `#with_params` method as default params to build or update the underlying page
143
+ object.
144
+
145
+ Below's an example of a situation where you would have a multi-store shop, and
146
+ want to scope all product categories and products pages depending on their
147
+ belonging store, given that some products and categories could exist in multiple
148
+ stores, requiring you to scope them.
149
+
150
+ ```ruby
151
+ Stores.each do |store|
152
+ with_params store_id: store.id, scope: :store_id do
153
+ store.product_categories.each do |product_category|
154
+ page :product_category, resource: product_category
155
+
156
+ product_category.products.each do |product|
157
+ page :product, resource: product
158
+ end
159
+ end
160
+ end
161
+ end
162
+ ```
163
+
164
+ Global skeleton-wide default params can also be passed to the
165
+ `Para::SeoTools::Skeleton.draw` call at the top of the skeleton file :
166
+
167
+ ```ruby
168
+ Para::SeoTools::Skeleton.draw(scope: :store_id) do
169
+ # You code scoped by store_id here
170
+ end
171
+ ```
172
+
173
+ #### Locales support
174
+
175
+ SeoTools comes with multi-locale support built-in. By default, each call to the
176
+ `page` method assigns the current `I18n.locale` to the created page resource.
177
+
178
+ Localized page path handling is dependent on your app logic, but you can easily
179
+ generate pages for each locale.
180
+
181
+ If routing to a specific locale only needs a `:locale` argument passed to your
182
+ URL helpers, and you want to create a page for each available locale, here how
183
+ you'd do it :
184
+
185
+ ```ruby
186
+ Para::SeoTools::Skeleton.draw(scope: :locale) do
187
+ I18n.available_locales.each do |locale|
188
+ I18n.with_locale(locale) do
189
+ Posts.find_each do |post|
190
+ page :post, resource: post, path: post_path(post, locale: locale)
191
+ end
192
+ end
193
+ end
194
+ ```
195
+
196
+ By using `I18n.with_locale`, we force the current locale in the block, and
197
+ SeoTools automatically assigns the locale to the page resource.
198
+
199
+ #### Lazy skeleton building.
200
+
201
+ On large applications, building the skeleton with all its pages at application
202
+ boot time is not an option. You can opt out from this strategy and choose to
203
+ build it yourself from a rake task by using the `Para::SeoTools::Skeleton.draw`
204
+ `:lazy` param and calling the rake task from a CRON or similar job.
205
+
206
+ ```ruby
207
+ Para::SeoTools::Skeleton.draw(lazy: true) do
208
+ # ...
209
+ end
210
+ ```
211
+
212
+ Then use the following rake task :
213
+
214
+ ```bash
215
+ rake seo_tools:skeleton:build
216
+ ```
217
+
218
+ #### Domain and subdomains handling
219
+
220
+ By default, SeoTools doesn't handle specifically domains and subdomains, since
221
+ it stores the page paths with a leading `/`.
222
+
223
+ You can tell it to take those parameters into account when building the
224
+ skeleton, and when fetching data during the request.
225
+
226
+ The first step is to activate one or both of domain and subdomain handling, use
227
+ the `#handle_domain` and `#handle_subdomain` in the para initializer file :
228
+
229
+ ```ruby
230
+ Para.config do |config|
231
+ config.seo_tools do |seo_tools|
232
+ seo_tools.handle_domain = true
233
+ seo_tools.handle_subdomain = true
234
+ end
235
+ end
236
+ ```
237
+
238
+ Then, you need to pass the domain and subdomain as parameters of the `#page`
239
+ call of your skeleton.rb, or with batch params assignation as described above
240
+ [Batch scoping and params forwarding](#batch-scoping-and-params-forwarding)
241
+
242
+ ```ruby
243
+ page :post, resource: post, subdomain: 'blog', domain: 'example.com'
244
+ ```
245
+
246
+ Now, when the page data is fetched during the request, the `request.subdomain`
247
+ and `request.domain` will be used.
248
+
249
+
90
250
  ### 2. Display the meta tags admin panel
91
251
 
92
252
  For the admin panel to display, all you'll need to do is create the component
@@ -103,7 +263,7 @@ section :your_section do
103
263
  end
104
264
  ```
105
265
 
106
- The go to the admin panel, and click the **Sitemap** menu link.
266
+ Then go to the admin panel, and click the **Sitemap** menu link.
107
267
 
108
268
  ### 3. Generate a sitemap.xml
109
269
 
@@ -3,9 +3,16 @@ module Para
3
3
  class Page < ActiveRecord::Base
4
4
  META_TAGS = :title, :description, :keywords, :image, :canonical
5
5
 
6
+ store_accessor :config, :scope
7
+
6
8
  has_attached_file :image, styles: { thumb: '200x200#' }
7
9
  validates_attachment :image, content_type: { content_type: /\Aimage\/.*\Z/ }
8
10
 
11
+ validate :identifier_uniqueness
12
+
13
+ scope :with_subdomain, ->(subdomain) { where("config->>'subdomain' = ?", subdomain) }
14
+ scope :with_domain, ->(domain) { where("config->>'domain' = ?", domain) }
15
+
9
16
  def meta_tag(name)
10
17
  if (value = send(name).presence) && (meta = process(name, value)).present?
11
18
  return meta
@@ -44,6 +51,29 @@ module Para
44
51
  Arel::Nodes::SqlLiteral.new(expr.to_sql)
45
52
  end
46
53
 
54
+ def scope_attributes
55
+ scope.each_with_object({}) do |attribute, hash|
56
+ hash[attribute] = if self.class.column_names.include?(attribute.to_s)
57
+ send(attribute)
58
+ else
59
+ config[attribute.to_s]
60
+ end
61
+ end
62
+ end
63
+
64
+ def sitemap_host
65
+ host = []
66
+ host << config['subdomain'] if Para::SeoTools.handle_subdomain
67
+
68
+ if Para::SeoTools.handle_domain
69
+ host << config['domain']
70
+ else
71
+ host << Para::SeoTools.host
72
+ end
73
+
74
+ host.join('.')
75
+ end
76
+
47
77
  private
48
78
 
49
79
  def process(name, value)
@@ -53,6 +83,23 @@ module Para
53
83
  value
54
84
  end
55
85
  end
86
+
87
+ def identifier_uniqueness
88
+ conditions = PageScoping.new(self).uniqueness_scope_conditions
89
+ conditions = conditions.where.not(id: id) if persisted?
90
+
91
+ if conditions.where(identifier: identifier).exists?
92
+ errors.add(:identifier, :taken)
93
+ end
94
+ end
95
+
96
+ def method_missing(method_name, *args, &block)
97
+ if config.key?(method_name.to_s)
98
+ config[method_name.to_s]
99
+ else
100
+ super
101
+ end
102
+ end
56
103
  end
57
104
  end
58
105
  end
@@ -0,0 +1,5 @@
1
+ class AddConfigToSeoToolsPages < ActiveRecord::Migration
2
+ def change
3
+ add_column :seo_tools_pages, :config, :jsonb
4
+ end
5
+ end
@@ -12,6 +12,7 @@ module Para
12
12
  autoload :Routes
13
13
  autoload :Skeleton
14
14
  autoload :Sitemap
15
+ autoload :PageScoping
15
16
 
16
17
  autoload :MetaTaggable
17
18
  autoload :MetaTaggableMacro
@@ -20,6 +21,12 @@ module Para
20
21
  mattr_writer :host
21
22
  @@host = ENV['APP_DOMAIN']
22
23
 
24
+ mattr_accessor :handle_domain
25
+ @@handle_domain = false
26
+
27
+ mattr_accessor :handle_subdomain
28
+ @@handle_subdomain = false
29
+
23
30
  mattr_accessor :title_methods
24
31
  @@title_methods = %w(title name)
25
32
 
@@ -37,7 +37,11 @@ module Para
37
37
  end
38
38
 
39
39
  def fetch_meta_tags_page
40
- if (page = Para::SeoTools::Page.find_by_path(request.path))
40
+ page_conditions = Para::SeoTools::Page.where(path: request.path)
41
+ page_conditions = page_conditions.with_subdomain(request.subdomain) if Para::SeoTools.handle_subdomain
42
+ page_conditions = page_conditions.with_domain(request.domain) if Para::SeoTools.handle_domain
43
+
44
+ if (page = page_conditions.first)
41
45
  set_meta_tags_from_page(page)
42
46
  end
43
47
  end
@@ -0,0 +1,39 @@
1
+ # Common class used by the Page model and PageBuilder to build and handle
2
+ # pages scoping and building their unique identifiers
3
+ #
4
+ module Para
5
+ module SeoTools
6
+ class PageScoping
7
+ attr_reader :resource
8
+
9
+ def initialize(resource)
10
+ @resource = resource
11
+ end
12
+
13
+ def scoped?
14
+ resource.scope.present?
15
+ end
16
+
17
+ def column?(attribute)
18
+ resource.class.column_names.include?(attribute.to_s)
19
+ end
20
+
21
+ def uniqueness_scope_conditions
22
+ return resource.class unless scoped?
23
+
24
+ resource.scope_attributes.reduce(resource.class) do |query, (attribute, value)|
25
+ if column?(attribute)
26
+ query.where(attribute => value)
27
+ else
28
+ query.where("config->>'#{ attribute }' = ?", value)
29
+ end
30
+ end
31
+ end
32
+
33
+ def unique_identifier
34
+ return resource.identifier unless scoped?
35
+ resource.scope_attributes.merge(identifier: resource.identifier).to_json
36
+ end
37
+ end
38
+ end
39
+ end
@@ -1,10 +1,33 @@
1
1
  module Para
2
2
  module SeoTools
3
3
  class Sitemap
4
+ def self.generate!
5
+ puts " * GENERATE SITEMAP ..."
6
+
7
+ build
8
+
9
+ puts " * BUILD ..."
10
+
11
+ ::Sitemap::Generator.instance.build!
12
+
13
+ puts " * SAVE ..."
14
+
15
+ ::Sitemap::Generator.instance.save(path)
16
+
17
+ puts " * SAVED !"
18
+ end
19
+
20
+ def self.path
21
+ @path ||= begin
22
+ root = ::Sitemap.configuration.save_path || ENV["LOCATION"] || Rails.public_path
23
+ File.join(root, "sitemap.xml")
24
+ end
25
+ end
26
+
4
27
  def self.build
5
- ::Sitemap::Generator.instance.load host: Para::SeoTools.host do
28
+ ::Sitemap::Generator.instance.load do
6
29
  Para::SeoTools::Page.find_each do |page|
7
- literal page.path
30
+ literal page.path, host: page.sitemap_host
8
31
  end
9
32
  end
10
33
  end
@@ -4,10 +4,10 @@ module Para
4
4
  extend ActiveSupport::Autoload
5
5
 
6
6
  autoload :Site
7
- autoload :Page
7
+ autoload :PageBuilder
8
8
  autoload :Worker
9
9
 
10
- mattr_accessor :site, :config, :enable_logging
10
+ mattr_accessor :site, :config, :options, :enable_logging
11
11
 
12
12
  def self.with_logging(&block)
13
13
  self.enable_logging = true
@@ -25,18 +25,23 @@ module Para
25
25
  require skeleton_path if File.exists?(skeleton_path)
26
26
  end
27
27
 
28
- def self.draw(lazy: false, &block)
28
+ def self.draw(lazy: false, **options, &block)
29
29
  self.config = block
30
+ self.options = options
30
31
  build unless lazy
31
32
  end
32
33
 
33
- def self.build
34
+ def self.build(load_skeleton: false)
34
35
  return if migrating?
35
36
  return unless ActiveRecord::Base.connection.table_exists?(Para::SeoTools::Page.table_name)
36
37
 
38
+ load if load_skeleton
39
+
37
40
  log " * Building app skeleton pages ..."
38
41
 
39
- self.site = Skeleton::Site.new(enable_logging: enable_logging)
42
+ site_options = options.merge(enable_logging: enable_logging)
43
+ self.site = Skeleton::Site.new(site_options)
44
+
40
45
  # Evaluate the configuration block
41
46
  site.instance_exec(&config)
42
47
 
@@ -1,27 +1,50 @@
1
1
  module Para
2
2
  module SeoTools
3
3
  module Skeleton
4
- class Page
4
+ class ScopeAttributeUndefined < StandardError; end
5
+
6
+ class PageBuilder
5
7
  include Rails.application.routes.url_helpers
6
8
 
7
- attr_reader :name, :resource, :options, :locale
9
+ attr_reader :name, :resource, :locale, :defaults, :config
8
10
 
9
11
  def initialize(name, path: nil, resource: nil, **options)
10
12
  @name = name
11
13
  @path = path
12
14
  @resource = resource
13
- @options = options
14
15
 
15
16
  # Fetch locale on page build to allow calling the `page` skeleton
16
17
  # method inside a `I18n.with_locale` block
17
18
  #
18
- @locale = options.fetch(:locale, I18n.locale)
19
+ @locale = options.delete(:locale) || I18n.locale
20
+ @defaults = options.delete(:defaults) || {}
21
+
22
+ # Remaining options will be stored as config in a JSONB attribute
23
+ @config = options
19
24
  end
20
25
 
21
26
  def identifier
22
27
  @identifier ||= [name, resource.try(:id)].compact.join(':')
23
28
  end
24
29
 
30
+ def scope
31
+ @scope ||= config[:scope]
32
+ end
33
+
34
+ def scope_attributes
35
+ scope.each_with_object({}) do |attribute, hash|
36
+ hash[attribute] = if (value = config[attribute])
37
+ value
38
+ elsif respond_to?(attribute)
39
+ send(attribute)
40
+ else
41
+ raise ScopeAttributeUndefined, "Your Skeleton page is scoped " +
42
+ "with the '#{ attribute }' attribute but you did not pass it " +
43
+ "as a parameter of the #page : #{ inspect }"
44
+ end
45
+ end
46
+ end
47
+
25
48
  def display_name
26
49
  @display_name ||= [name, resource.try(:id)].compact.join(' #').humanize
27
50
  end
@@ -31,18 +54,13 @@ module Para
31
54
  end
32
55
 
33
56
  def model
34
- @model ||= self.class.model_for(identifier).tap do |page|
57
+ @model ||= self.class.model_for(self).tap do |page|
35
58
  # Override path (i.e.: slug changed)
36
59
  page.path = path if path.to_s != page.path
37
60
  page.locale = locale
38
61
  # 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)
62
+ page.defaults = defaults
63
+ page.config = config
46
64
  end
47
65
  end
48
66
 
@@ -56,15 +74,21 @@ module Para
56
74
  end
57
75
  end
58
76
 
59
- def self.model_for(identifier)
60
- models[identifier] ||= ::Para::SeoTools::Page.new(identifier: identifier)
77
+ def self.model_for(page)
78
+ models[unique_identifier_for(page)] ||= ::Para::SeoTools::Page.new(
79
+ identifier: page.identifier
80
+ )
61
81
  end
62
82
 
63
83
  def self.models
64
84
  @models ||= Para::SeoTools::Page.all.each_with_object({}) do |page, hash|
65
- hash[page.identifier] = page
85
+ hash[unique_identifier_for(page)] = page
66
86
  end
67
87
  end
88
+
89
+ def self.unique_identifier_for(page)
90
+ Para::SeoTools::PageScoping.new(page).unique_identifier
91
+ end
68
92
  end
69
93
  end
70
94
  end
@@ -3,19 +3,29 @@ module Para
3
3
  module Skeleton
4
4
  class Site
5
5
  include Rails.application.routes.url_helpers
6
- attr_reader :enable_logging
6
+ attr_reader :enable_logging, :default_page_options
7
7
 
8
8
  def initialize(options = {})
9
- @enable_logging = options[:enable_logging]
9
+ @enable_logging = options.delete(:enable_logging)
10
+ @default_page_options = options
10
11
  end
11
12
 
12
13
  def page(name, options = {} , &block)
13
- Skeleton::Page.new(name, options).tap do |page|
14
+ options.reverse_merge!(default_page_options)
15
+
16
+ Skeleton::PageBuilder.new(name, options).tap do |page|
14
17
  pages << page
15
18
  save if pages.length == max_pages_before_save
16
19
  end
17
20
  end
18
21
 
22
+ def with_params(params = {}, &block)
23
+ previous_default_page_options = default_page_options
24
+ @default_page_options = default_page_options.dup.merge(params)
25
+ block.call
26
+ @default_page_options = previous_default_page_options
27
+ end
28
+
19
29
  def pages
20
30
  @pages ||= []
21
31
  end
@@ -1,5 +1,5 @@
1
1
  module Para
2
2
  module SeoTools
3
- VERSION = "0.3.0"
3
+ VERSION = "0.4.0"
4
4
  end
5
5
  end
@@ -0,0 +1,22 @@
1
+ namespace :para do
2
+ namespace :seo_tools do
3
+ namespace :skeleton do
4
+ desc "Builds or refreshes the app skeleton."
5
+ task build: :environment do
6
+ Para::SeoTools::Skeleton.build(load_skeleton: true)
7
+ end
8
+ end
9
+
10
+ namespace :sitemap do
11
+ desc "Generates a new sitemap."
12
+ task generate: :environment do
13
+ Para::SeoTools::Sitemap.generate!
14
+ end
15
+
16
+ desc "Ping engines."
17
+ task ping: :environment do
18
+ ::Sitemap::Ping.send_request(Para::SeoTools::Sitemap.path)
19
+ end
20
+ end
21
+ end
22
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: para-seo_tools
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Valentin Ballestrino
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-09-27 00:00:00.000000000 Z
11
+ date: 2016-10-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -118,6 +118,7 @@ files:
118
118
  - db/migrate/20150601085253_create_seo_tools_pages.rb
119
119
  - db/migrate/20160610094032_add_meta_tags_to_seo_tools_pages.rb
120
120
  - db/migrate/20160614081242_add_locale_and_remove_meta_tags_list_to_seo_tools_pages.rb
121
+ - db/migrate/20160930064950_add_config_to_seo_tools_pages.rb
121
122
  - lib/generators/para/seo_tools/install/install_generator.rb
122
123
  - lib/generators/para/seo_tools/install/templates/initializer.rb
123
124
  - lib/generators/para/seo_tools/install/templates/skeleton.rb
@@ -138,17 +139,17 @@ files:
138
139
  - lib/para/seo_tools/meta_tags/vendors/base.rb
139
140
  - lib/para/seo_tools/meta_tags/vendors/open_graph.rb
140
141
  - lib/para/seo_tools/meta_tags/vendors/twitter.rb
142
+ - lib/para/seo_tools/page_scoping.rb
141
143
  - lib/para/seo_tools/routes.rb
142
144
  - lib/para/seo_tools/sitemap.rb
143
145
  - lib/para/seo_tools/skeleton.rb
144
- - lib/para/seo_tools/skeleton/page.rb
146
+ - lib/para/seo_tools/skeleton/page_builder.rb
145
147
  - lib/para/seo_tools/skeleton/site.rb
146
148
  - lib/para/seo_tools/skeleton/worker.rb
147
149
  - lib/para/seo_tools/version.rb
148
150
  - lib/para/seo_tools/view_helpers.rb
149
151
  - lib/tasks/migration.rake
150
- - lib/tasks/sitemap.rake
151
- - lib/tasks/skeleton.rake
152
+ - lib/tasks/para_seo_tools_tasks.rake
152
153
  - para-seo_tools.gemspec
153
154
  - test/fixtures/seo_tools/pages.yml
154
155
  - test/models/seo_tools/page_test.rb
@@ -1,17 +0,0 @@
1
- namespace :seo_tools do
2
- namespace :sitemap do
3
- desc "Generates a new sitemap."
4
- task generate: :environment do
5
- Para::SeoTools::Sitemap.build
6
- root = ::Sitemap.configuration.save_path || ENV["LOCATION"] || Rails.public_path
7
- path = File.join(root, "sitemap.xml")
8
- ::Sitemap::Generator.instance.build!
9
- ::Sitemap::Generator.instance.save(path)
10
- end
11
-
12
- desc "Ping engines."
13
- task ping: :environment do
14
- ::Sitemap::Ping.send_request(ENV["LOCATION"])
15
- end
16
- end
17
- end
@@ -1,9 +0,0 @@
1
- namespace :seo_tools do
2
- namespace :skeleton do
3
- desc "Builds or refreshes the app skeleton."
4
- task build: :environment do
5
- Para::SeoTools::Skeleton.load
6
- Para::SeoTools::Skeleton::Worker.perform
7
- end
8
- end
9
- end