sitemap 0.1 → 0.2b

Sign up to get free protection for your applications and to get access to all the features.
@@ -43,10 +43,6 @@ You may change the defaults for either <tt>params</tt> or <tt>search</tt> option
43
43
 
44
44
  Sitemap.defaults[:params] = { :format => "html" }
45
45
 
46
- == Limitations
47
-
48
- Sitemaps can only have up to 50000 urls and an uncompressed size of 10MB. This issue will be resolved in a future release.
49
-
50
46
  == License
51
47
 
52
48
  This package is licensed under the MIT license and/or the Creative
@@ -9,6 +9,7 @@ require "singleton"
9
9
  require "builder"
10
10
  require "sitemap/railtie"
11
11
  require "sitemap/ping"
12
+ require "sitemap/store"
12
13
  require "sitemap/generator"
13
14
 
14
15
  module Sitemap
@@ -20,8 +21,12 @@ module Sitemap
20
21
  self.defaults = {
21
22
  :params => {},
22
23
  :search => {
23
- :updated_at => proc { |obj| obj.updated_at.strftime("%Y-%m-%d") if obj.respond_to?(:updated_at) }
24
- }
24
+ :updated_at => proc { |obj|
25
+ obj.updated_at.strftime("%Y-%m-%d") if obj.respond_to?(:updated_at)
26
+ }
27
+ },
28
+ :query_batch_size => 500,
29
+ :max_urls => 10000
25
30
  }
26
31
 
27
32
  end
@@ -10,13 +10,17 @@ module Sitemap
10
10
  :priority => "priority"
11
11
  }
12
12
 
13
- attr_accessor :entries, :host, :routes
13
+ attr_accessor :store, :host, :routes, :fragments
14
14
 
15
15
  # Instantiates a new object.
16
16
  # Should never be called directly.
17
17
  def initialize
18
18
  self.class.send(:include, Rails.application.routes.url_helpers)
19
- self.entries = []
19
+ self.fragments = []
20
+ self.store = Store.new(:max_entries => Sitemap.defaults[:max_urls])
21
+ self.store.before_reset do |entries|
22
+ self.process_fragment!
23
+ end
20
24
  end
21
25
 
22
26
  # Sets the urls to be indexed.
@@ -75,7 +79,7 @@ module Sitemap
75
79
  search = Sitemap.defaults[:search].clone.merge!(options.select { |k, v| SEARCH_ATTRIBUTES.keys.include?(k) })
76
80
  search.merge!(search) { |type, value| get_data(object, value) }
77
81
 
78
- self.entries << {
82
+ self.store << {
79
83
  :object => object,
80
84
  :search => search,
81
85
  :params => params
@@ -106,32 +110,65 @@ module Sitemap
106
110
  #
107
111
  def resources(type, options = {})
108
112
  path(type) unless options[:skip_index]
109
- objects = options[:objects] ? options[:objects].call : type.to_s.classify.constantize.all
110
- options.reject! { |k, v| k == :objects }
111
-
112
- objects.each do |object|
113
- path(object, options)
113
+ link_params = options.reject { |k, v| k == :objects }
114
+ get_objects = lambda {
115
+ options[:objects] ? options[:objects].call : type.to_s.classify.constantize
116
+ }
117
+ get_objects.call.find_each(:batch_size => Sitemap.defaults[:query_batch_size]) do |object|
118
+ path(object, link_params)
114
119
  end
115
120
  end
116
121
 
117
122
  # Parses the loaded data and returns the xml entries.
118
- def build
119
- instance_exec(self, &routes)
123
+ def render(object = "fragment")
120
124
  xml = Builder::XmlMarkup.new(:indent => 2)
121
- file = File.read(File.expand_path("../../views/index.xml.builder", __FILE__))
125
+ file = File.read(File.expand_path("../../views/#{object}.xml.builder", __FILE__))
122
126
  instance_eval file
123
127
  end
124
128
 
125
- # Builds xml entries and saves the data to the specified location.
126
- def save(location)
127
- file = File.new(location, "w")
128
- file.write(build)
129
+ # Creates a temporary file from the existing entries.
130
+ def process_fragment!
131
+ file = Tempfile.new("sitemap.xml")
132
+ file.write(render)
129
133
  file.close
134
+ self.fragments << file
135
+ end
136
+
137
+ # Generates fragments.
138
+ def build!
139
+ instance_exec(self, &routes)
140
+ process_fragment! unless store.entries.empty?
141
+ end
142
+
143
+ # Creates the sitemap index file and saves any existing fragments.
144
+ def save(location)
145
+ if fragments.length == 1
146
+ FileUtils.mv(fragments.first.path, location)
147
+ else
148
+ remove_saved_files(location)
149
+ root = File.join(Pathname.new(location).dirname, "sitemap")
150
+ Dir.mkdir(root) unless File.directory?(root)
151
+ fragments.each_with_index do |fragment, i|
152
+ file_pattern = File.join(root, "sitemap-fragment-#{i + 1}.xml")
153
+ FileUtils.mv(fragment.path, file_pattern)
154
+ end
155
+ file = File.new(location, "w")
156
+ file.write(render "index")
157
+ file.close
158
+ end
159
+ end
160
+
161
+ # URL to the sitemap file.
162
+ #
163
+ # Defaults to <tt>sitemap.xml</tt>.
164
+ def file_url(path = "sitemap.xml")
165
+ URI::HTTP.build(:host => host, :path => File.join("/", path)).to_s
130
166
  end
131
167
 
132
- # URL to the <tt>sitemap.xml</tt> file.
133
- def file_url
134
- URI::HTTP.build(:host => host, :path => "/sitemap.xml").to_s
168
+ def remove_saved_files(location)
169
+ root = File.join(Pathname.new(location).dirname, "sitemap")
170
+ Dir[File.join(root, "sitemap-fragment-*.xml")].each { |file| File.unlink(file) }
171
+ File.unlink(location) if File.exist?(location)
135
172
  end
136
173
 
137
174
  private
@@ -0,0 +1,30 @@
1
+ module Sitemap
2
+
3
+ class Store
4
+
5
+ attr_accessor :entries, :max_entries, :reset_count, :before_reset_callback
6
+
7
+ def initialize(options = {})
8
+ self.entries = []
9
+ self.reset_count = 0
10
+ self.max_entries = options[:max_entries]
11
+ end
12
+
13
+ def << entry
14
+ reset! if entries.length >= max_entries
15
+ self.entries << entry
16
+ end
17
+
18
+ def reset!
19
+ before_reset_callback.call(entries) if before_reset_callback
20
+ self.entries = []
21
+ self.reset_count += 1
22
+ end
23
+
24
+ def before_reset(&block)
25
+ self.before_reset_callback = block
26
+ end
27
+
28
+ end
29
+
30
+ end
@@ -8,6 +8,7 @@ namespace :sitemap do
8
8
  task :generate => :environment do
9
9
  setup
10
10
  path = File.join(Rails.public_path, "sitemap.xml")
11
+ Sitemap::Generator.instance.build!
11
12
  Sitemap::Generator.instance.save path
12
13
  end
13
14
 
@@ -0,0 +1,15 @@
1
+ xml.instruct!
2
+ xml.urlset :xmlns => "http://www.sitemaps.org/schemas/sitemap/0.9" do
3
+
4
+ store.entries.each do |entry|
5
+ xml.url do
6
+ xml.loc polymorphic_url(entry[:object], entry[:params])
7
+ entry[:search].each do |type, value|
8
+ next if !value || value.blank?
9
+ xml.tag! SEARCH_ATTRIBUTES[type], value.to_s
10
+ end
11
+ end
12
+ end
13
+
14
+ end
15
+
@@ -1,15 +1,11 @@
1
1
  xml.instruct!
2
- xml.urlset :xmlns => "http://www.sitemaps.org/schemas/sitemap/0.9" do
2
+ xml.sitemapindex :xmlns => "http://www.sitemaps.org/schemas/sitemap/0.9" do
3
3
 
4
- entries.each do |entry|
5
- xml.url do
6
- xml.loc polymorphic_url(entry[:object], entry[:params])
7
- entry[:search].each do |type, value|
8
- next if !value || value.blank?
9
- xml.tag! SEARCH_ATTRIBUTES[type], value.to_s
10
- end
4
+ fragments.each_with_index do |fragment, i|
5
+ xml.sitemap do
6
+ xml.loc file_url("sitemaps/sitemap-fragment-#{i + 1}.xml")
7
+ xml.lastmod Time.now.strftime("%Y-%m-%dT%H:%M:%S+00:00")
11
8
  end
12
9
  end
13
10
 
14
11
  end
15
-
@@ -2,7 +2,7 @@ $LOAD_PATH << File.join(File.dirname(__FILE__), "lib")
2
2
 
3
3
  spec = Gem::Specification.new do |spec|
4
4
  spec.name = "sitemap"
5
- spec.version = "0.1"
5
+ spec.version = "0.2b"
6
6
  spec.summary = "Sitemap"
7
7
  spec.description = "A simple ruby on rails sitemap generator"
8
8
 
@@ -27,6 +27,7 @@ module SitemapTestSetup
27
27
  t.string :name
28
28
  t.text :contents
29
29
  t.string :location
30
+ t.boolean :published, :default => true
30
31
  t.timestamps
31
32
  end
32
33
  end
@@ -34,7 +35,8 @@ module SitemapTestSetup
34
35
  options = {
35
36
  :name => "Coding #{i}",
36
37
  :contents => "Lorem ipsum dolor sit",
37
- :location => "someplace-#{i}"
38
+ :location => "someplace-#{i}",
39
+ :published => (i < 6)
38
40
  }
39
41
  Activity.create!(options)
40
42
  end
@@ -19,6 +19,7 @@ class SitemapTest < Test::Unit::TestCase
19
19
 
20
20
  def setup
21
21
  create_db
22
+ Sitemap.defaults[:max_urls] = 10000
22
23
  Sitemap::Generator.reset_instance
23
24
  end
24
25
 
@@ -28,7 +29,7 @@ class SitemapTest < Test::Unit::TestCase
28
29
 
29
30
  def test_xml_response
30
31
  Sitemap::Generator.instance.load(:host => "someplace.com") {}
31
- doc = Nokogiri::XML(Sitemap::Generator.instance.build)
32
+ doc = Nokogiri::XML(Sitemap::Generator.instance.render)
32
33
  assert doc.errors.empty?
33
34
  assert_equal doc.root.name, "urlset"
34
35
  end
@@ -39,7 +40,8 @@ class SitemapTest < Test::Unit::TestCase
39
40
  path :root
40
41
  path :faq
41
42
  end
42
- doc = Nokogiri::HTML(Sitemap::Generator.instance.build)
43
+ Sitemap::Generator.instance.build!
44
+ doc = Nokogiri::HTML(Sitemap::Generator.instance.render)
43
45
  elements = doc.xpath "//url/loc"
44
46
  assert_equal elements.length, urls.length
45
47
  elements.each_with_index do |element, i|
@@ -51,7 +53,8 @@ class SitemapTest < Test::Unit::TestCase
51
53
  Sitemap::Generator.instance.load(:host => "someplace.com") do
52
54
  resources :activities
53
55
  end
54
- doc = Nokogiri::HTML(Sitemap::Generator.instance.build)
56
+ Sitemap::Generator.instance.build!
57
+ doc = Nokogiri::HTML(Sitemap::Generator.instance.render)
55
58
  elements = doc.xpath "//url/loc"
56
59
  assert_equal elements.length, Activity.count + 1
57
60
  assert_equal elements.first.text, "http://someplace.com/activities"
@@ -61,14 +64,15 @@ class SitemapTest < Test::Unit::TestCase
61
64
  end
62
65
 
63
66
  def test_custom_resource_objects
64
- activities = [Activity.first, Activity.last]
67
+ activities = proc { Activity.where(:published => true) }
65
68
  Sitemap::Generator.instance.load(:host => "someplace.com") do
66
- resources :activities, :objects => proc { activities }, :skip_index => true
69
+ resources :activities, :objects => activities, :skip_index => true
67
70
  end
68
- doc = Nokogiri::HTML(Sitemap::Generator.instance.build)
71
+ Sitemap::Generator.instance.build!
72
+ doc = Nokogiri::HTML(Sitemap::Generator.instance.render)
69
73
  elements = doc.xpath "//url/loc"
70
- assert_equal elements.length, activities.length
71
- activities.each_with_index do |activity, i|
74
+ assert_equal elements.length, activities.call.length
75
+ activities.call.each_with_index do |activity, i|
72
76
  assert_equal elements[i].text, "http://someplace.com/activities/%d" % activity.id
73
77
  end
74
78
  end
@@ -77,7 +81,8 @@ class SitemapTest < Test::Unit::TestCase
77
81
  Sitemap::Generator.instance.load(:host => "someplace.com") do
78
82
  path :faq, :params => { :host => "anotherplace.com", :format => "html", :filter => "recent" }
79
83
  end
80
- doc = Nokogiri::HTML(Sitemap::Generator.instance.build)
84
+ Sitemap::Generator.instance.build!
85
+ doc = Nokogiri::HTML(Sitemap::Generator.instance.render)
81
86
  elements = doc.xpath "//url/loc"
82
87
  assert_equal elements.first.text, "http://anotherplace.com/questions.html?filter=recent"
83
88
  end
@@ -87,7 +92,8 @@ class SitemapTest < Test::Unit::TestCase
87
92
  resources :activities, :skip_index => true, :params => { :host => proc { |obj| [obj.location, host].join(".") } }
88
93
  end
89
94
  activities = Activity.all
90
- doc = Nokogiri::HTML(Sitemap::Generator.instance.build)
95
+ Sitemap::Generator.instance.build!
96
+ doc = Nokogiri::HTML(Sitemap::Generator.instance.render)
91
97
  elements = doc.xpath "//url/loc"
92
98
  elements.each_with_index do |element, i|
93
99
  assert_equal element.text, "http://%s.someplace.com/activities/%d" % [activities[i].location, activities[i].id]
@@ -99,7 +105,8 @@ class SitemapTest < Test::Unit::TestCase
99
105
  path :faq, :priority => 1, :change_frequency => "always"
100
106
  resources :activities, :change_frequency => "weekly"
101
107
  end
102
- doc = Nokogiri::HTML(Sitemap::Generator.instance.build)
108
+ Sitemap::Generator.instance.build!
109
+ doc = Nokogiri::HTML(Sitemap::Generator.instance.render)
103
110
  assert_equal doc.xpath("//url/priority").first.text, "1"
104
111
  elements = doc.xpath "//url/changefreq"
105
112
  assert_equal elements[0].text, "always"
@@ -113,7 +120,7 @@ class SitemapTest < Test::Unit::TestCase
113
120
  resources :activities, :priority => proc { |obj| obj.id <= 2 ? 1 : 0.5 }, :skip_index => true
114
121
  end
115
122
  activities = Activity.all
116
- doc = Nokogiri::HTML(Sitemap::Generator.instance.build)
123
+ doc = Nokogiri::HTML(Sitemap::Generator.instance.render)
117
124
  elements = doc.xpath "//url/priority"
118
125
  elements.each_with_index do |element, i|
119
126
  value = activities[i].id <= 2 ? "1" : "0.5"
@@ -125,7 +132,8 @@ class SitemapTest < Test::Unit::TestCase
125
132
  Sitemap::Generator.instance.load(:host => "someplace.com") do
126
133
  path :faq, :priority => "", :change_frequency => lambda { |e| return false}, :updated_at => Date.today
127
134
  end
128
- doc = Nokogiri::HTML(Sitemap::Generator.instance.build)
135
+ Sitemap::Generator.instance.build!
136
+ doc = Nokogiri::HTML(Sitemap::Generator.instance.render)
129
137
  assert_equal doc.xpath("//url/priority").count, 0
130
138
  assert_equal doc.xpath("//url/changefreq").count, 0
131
139
  assert_equal doc.xpath("//url/lastmod").text, Date.today.to_s
@@ -136,4 +144,50 @@ class SitemapTest < Test::Unit::TestCase
136
144
  assert_equal Sitemap::Generator.instance.file_url, "http://someplace.com/sitemap.xml"
137
145
  end
138
146
 
147
+ def test_save_creates_file
148
+ path = File.join(Dir.tmpdir, "sitemap.xml")
149
+ File.unlink(path) if File.exist?(path)
150
+ Sitemap::Generator.instance.load(:host => "someplace.com") do
151
+ resources :activities
152
+ end
153
+ Sitemap::Generator.instance.build!
154
+ Sitemap::Generator.instance.save(path)
155
+ assert File.exist?(path)
156
+ File.unlink(path)
157
+ end
158
+
159
+ def test_saves_fragments
160
+ Sitemap.defaults[:max_urls] = 2
161
+ Sitemap::Generator.instance.load(:host => "someplace.com") do
162
+ path :root
163
+ path :root
164
+ path :root
165
+ path :root
166
+ end
167
+ path = File.join(Dir.tmpdir, "sitemap.xml")
168
+ root = File.join(Dir.tmpdir, "sitemap") # Directory is being removed at the end of the test.
169
+ assert !File.directory?(root)
170
+ Sitemap::Generator.instance.build!
171
+ Sitemap::Generator.instance.save(path)
172
+ 1.upto(2) { |i|
173
+ assert File.exists?(File.join(root, "sitemap-fragment-#{i}.xml"))
174
+ }
175
+ FileUtils.rm_rf(root)
176
+ end
177
+
178
+ def test_fragments_index
179
+ Sitemap.defaults[:max_urls] = 2
180
+ Sitemap::Generator.instance.load(:host => "someplace.com") do
181
+ path :root
182
+ path :root
183
+ path :root
184
+ path :root
185
+ path :root
186
+ end
187
+ Sitemap::Generator.instance.build!
188
+ doc = Nokogiri::HTML(Sitemap::Generator.instance.render("index"))
189
+ elements = doc.xpath "//sitemap"
190
+ assert_equal Sitemap::Generator.instance.fragments.length, 3
191
+ end
192
+
139
193
  end
@@ -0,0 +1,35 @@
1
+ require "test/unit"
2
+ require "rubygems"
3
+
4
+ class StoreTest < Test::Unit::TestCase
5
+
6
+ def test_append_entries
7
+ store = Sitemap::Store.new(:max_entries => 1000)
8
+ 3.times { store << "contents" }
9
+ assert_equal store.entries.length, 3
10
+ end
11
+
12
+ def test_reset_entries_limit
13
+ store = Sitemap::Store.new(:max_entries => 2)
14
+ 2.times { store << "contents" }
15
+ assert_equal store.entries.length, 2
16
+ store << "contents"
17
+ assert_equal store.entries.length, 1
18
+ end
19
+
20
+ def test_reset_callback
21
+ store = Sitemap::Store.new(:max_entries => 2)
22
+ store.before_reset do |entries|
23
+ store.instance_variable_set("@callback_data", entries.join(", "))
24
+ end
25
+ 3.times { |i| store << "item #{i + 1}" }
26
+ assert_equal store.instance_variable_get("@callback_data"), "item 1, item 2"
27
+ end
28
+
29
+ def test_increments_reset_count
30
+ store = Sitemap::Store.new(:max_entries => 2)
31
+ 5.times { store << "contents" }
32
+ assert_equal store.reset_count, 2
33
+ end
34
+
35
+ end
metadata CHANGED
@@ -1,19 +1,19 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sitemap
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.1'
5
- prerelease:
4
+ version: 0.2b
5
+ prerelease: 3
6
6
  platform: ruby
7
7
  authors:
8
8
  - Daniel Mircea
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-09-10 00:00:00.000000000Z
12
+ date: 2012-03-02 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: sqlite3
16
- requirement: &17823580 !ruby/object:Gem::Requirement
16
+ requirement: &13114780 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *17823580
24
+ version_requirements: *13114780
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: nokogiri
27
- requirement: &17823140 !ruby/object:Gem::Requirement
27
+ requirement: &13114300 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,7 +32,7 @@ dependencies:
32
32
  version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *17823140
35
+ version_requirements: *13114300
36
36
  description: A simple ruby on rails sitemap generator
37
37
  email: daniel@viseztrance.com
38
38
  executables: []
@@ -41,21 +41,24 @@ extra_rdoc_files:
41
41
  - README.rdoc
42
42
  - LICENSE
43
43
  files:
44
- - lib/generators/sitemap/USAGE
45
- - lib/generators/sitemap/templates/sitemap.rb
46
- - lib/generators/sitemap/install_generator.rb
47
- - lib/views/index.xml.builder
48
- - lib/sitemap.rb
44
+ - lib/tasks/sitemap.rake
45
+ - lib/sitemap/ping.rb
49
46
  - lib/sitemap/railtie.rb
47
+ - lib/sitemap/store.rb
50
48
  - lib/sitemap/generator.rb
51
- - lib/sitemap/ping.rb
52
- - lib/tasks/sitemap.rake
49
+ - lib/views/index.xml.builder
50
+ - lib/views/fragment.xml.builder
51
+ - lib/sitemap.rb
52
+ - lib/generators/sitemap/USAGE
53
+ - lib/generators/sitemap/install_generator.rb
54
+ - lib/generators/sitemap/templates/sitemap.rb
53
55
  - README.rdoc
54
56
  - LICENSE
55
57
  - Rakefile
56
58
  - sitemap.gemspec
57
59
  - test/singleton.rb
58
60
  - test/setup.rb
61
+ - test/store_test.rb
59
62
  - test/sitemap_test.rb
60
63
  homepage: http://github.com/viseztrance/rails-sitemap
61
64
  licenses: []
@@ -77,16 +80,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
77
80
  required_rubygems_version: !ruby/object:Gem::Requirement
78
81
  none: false
79
82
  requirements:
80
- - - ! '>='
83
+ - - ! '>'
81
84
  - !ruby/object:Gem::Version
82
- version: '0'
85
+ version: 1.3.1
83
86
  requirements: []
84
87
  rubyforge_project:
85
- rubygems_version: 1.8.8
88
+ rubygems_version: 1.8.10
86
89
  signing_key:
87
90
  specification_version: 3
88
91
  summary: Sitemap
89
92
  test_files:
90
93
  - test/singleton.rb
91
94
  - test/setup.rb
95
+ - test/store_test.rb
92
96
  - test/sitemap_test.rb
97
+ has_rdoc: true