Commit 1a2f1931f49d544f3d636a8e3df925dc871b73c5
Committed by
Antonio Terceiro
1 parent
4a74f898
Exists in
staging
and in
42 other branches
ActionItem935: external blog repeater
Showing
32 changed files
with
412 additions
and
85 deletions
Show diff stats
app/helpers/blog_helper.rb
app/helpers/content_viewer_helper.rb
... | ... | @@ -20,7 +20,7 @@ module ContentViewerHelper |
20 | 20 | unless args[:no_link] |
21 | 21 | title = content_tag('h3', link_to(article.name, article.url), :class => 'title') |
22 | 22 | end |
23 | - title << content_tag('span', _("%s, by %s") % [show_date(article.created_at), link_to(article.author.name, article.author.url)], :class => 'created-at') | |
23 | + title << content_tag('span', _("%s, by %s") % [show_date(article.published_at), link_to(article.author.name, article.author.url)], :class => 'created-at') | |
24 | 24 | end |
25 | 25 | title |
26 | 26 | end | ... | ... |
app/models/article.rb
... | ... | @@ -20,6 +20,10 @@ class Article < ActiveRecord::Base |
20 | 20 | |
21 | 21 | settings_items :display_hits, :type => :boolean, :default => true |
22 | 22 | |
23 | + before_create do |article| | |
24 | + article.published_at = article.created_at if article.published_at.nil? | |
25 | + end | |
26 | + | |
23 | 27 | def self.human_attribute_name(attrib) |
24 | 28 | case attrib.to_sym |
25 | 29 | when :name | ... | ... |
app/models/blog.rb
1 | 1 | class Blog < Folder |
2 | 2 | |
3 | - has_many :posts, :class_name => 'Article', :foreign_key => 'parent_id', :source => :children, :conditions => [ 'type != ?', 'RssFeed' ], :order => 'created_at DESC' | |
3 | + has_many :posts, :class_name => 'Article', :foreign_key => 'parent_id', :source => :children, :conditions => [ 'type != ?', 'RssFeed' ], :order => 'published_at DESC' | |
4 | 4 | |
5 | 5 | attr_accessor :feed_attrs |
6 | 6 | attr_accessor :filter |
... | ... | @@ -56,7 +56,7 @@ class Blog < Folder |
56 | 56 | article = self |
57 | 57 | children = if filter and filter[:year] and filter[:month] |
58 | 58 | filter_date = DateTime.parse("#{filter[:year]}-#{filter[:month]}-01") |
59 | - posts.paginate :page => npage, :per_page => posts_per_page, :conditions => [ 'created_at between ? and ?', filter_date, filter_date + 1.month - 1.day ] | |
59 | + posts.paginate :page => npage, :per_page => posts_per_page, :conditions => [ 'published_at between ? and ?', filter_date, filter_date + 1.month - 1.day ] | |
60 | 60 | else |
61 | 61 | posts.paginate :page => npage, :per_page => posts_per_page |
62 | 62 | end |
... | ... | @@ -64,4 +64,33 @@ class Blog < Folder |
64 | 64 | render :file => 'content_viewer/blog_page', :locals => {:article => article, :children => children} |
65 | 65 | end |
66 | 66 | end |
67 | + | |
68 | + has_one :external_feed, :foreign_key => 'blog_id' | |
69 | + | |
70 | + attr_accessor :external_feed_data | |
71 | + def external_feed_builder=(efeed) | |
72 | + self.external_feed_data = efeed | |
73 | + end | |
74 | + | |
75 | + def validate | |
76 | + unless self.external_feed_data.nil? | |
77 | + if self.external_feed(true) && self.external_feed.id == self.external_feed_data[:id].to_i | |
78 | + self.external_feed.attributes = self.external_feed_data | |
79 | + else | |
80 | + self.build_external_feed(self.external_feed_data) | |
81 | + end | |
82 | + self.external_feed.valid? | |
83 | + self.external_feed.errors.delete(:blog_id) # dont validate here relation: external_feed <-> blog | |
84 | + self.external_feed.errors.each do |attr,msg| | |
85 | + self.errors.add(attr, msg) | |
86 | + end | |
87 | + end | |
88 | + end | |
89 | + | |
90 | + after_save do |blog| | |
91 | + if blog.external_feed | |
92 | + blog.external_feed.save | |
93 | + end | |
94 | + end | |
95 | + | |
67 | 96 | end | ... | ... |
app/models/blog_archives_block.rb
... | ... | @@ -18,10 +18,10 @@ class BlogArchivesBlock < Block |
18 | 18 | return nil unless owner.has_blog? |
19 | 19 | results = '' |
20 | 20 | posts = owner.blog.posts |
21 | - posts.group_by{|i| i.created_at.year}.each do |year, results_by_year| | |
21 | + posts.group_by{|i| i.published_at.year}.each do |year, results_by_year| | |
22 | 22 | results << content_tag('li', content_tag('strong', "#{year} (#{results_by_year.size})")) |
23 | 23 | results << "<ul class='#{year}-archive'>" |
24 | - results_by_year.group_by{|i| [ ('%02d' % i.created_at.month()), gettext(MONTHS[i.created_at.month() - 1])]}.sort.each do |month, results_by_month| | |
24 | + results_by_year.group_by{|i| [ ('%02d' % i.published_at.month()), gettext(MONTHS[i.published_at.month() - 1])]}.sort.each do |month, results_by_month| | |
25 | 25 | results << content_tag('li', link_to("#{month[1]} (#{results_by_month.size})", owner.generate_url(:controller => 'content_viewer', :action => 'view_page', :page => [owner.blog.path, year, month[0]]))) |
26 | 26 | end |
27 | 27 | results << "</ul>" | ... | ... |
... | ... | @@ -0,0 +1,29 @@ |
1 | +class ExternalFeed < ActiveRecord::Base | |
2 | + | |
3 | + belongs_to :blog | |
4 | + validates_presence_of :blog_id | |
5 | + validates_presence_of :address, :if => lambda {|efeed| efeed.enabled} | |
6 | + validates_uniqueness_of :blog_id | |
7 | + | |
8 | + def add_item(title, link, date, content) | |
9 | + article = TinyMceArticle.new(:name => title, :profile => blog.profile, :body => content, :published_at => date, :source => link, :profile => blog.profile, :parent => blog) | |
10 | + unless blog.children.exists?(:slug => article.slug) | |
11 | + article.save! | |
12 | + end | |
13 | + end | |
14 | + | |
15 | + def clear | |
16 | + # do nothing | |
17 | + end | |
18 | + def finish_fetch | |
19 | + if self.only_once | |
20 | + self.enabled = 'false' | |
21 | + end | |
22 | + self.save! | |
23 | + end | |
24 | + | |
25 | + def limit | |
26 | + 0 | |
27 | + end | |
28 | + | |
29 | +end | ... | ... |
app/models/feed_reader_block.rb
app/views/cms/_blog.rhtml
1 | +<%= error_messages_for 'blog' %> | |
2 | + | |
1 | 3 | <h3><%= _('My Blog') %></h3> |
2 | 4 | |
3 | 5 | <%= render :file => 'shared/tiny_mce' %> |
... | ... | @@ -8,12 +10,26 @@ |
8 | 10 | |
9 | 11 | <%= labelled_form_field(_('Posts per page:'), f.select(:posts_per_page, [5, 10, 20, 50, 100])) %> |
10 | 12 | |
11 | -<% fields_for 'article[feed]', @article.feed do |feed| %> | |
12 | - | |
13 | -<%= labelled_form_field(_('Limit of posts in RSS Feed'), feed.select(:limit, ['5', '10', '20', '50'])) %> | |
14 | - | |
15 | -<%= labelled_form_field(_('Use as description in RSS Feed:'), feed.select(:feed_item_description, [ [ _('Article abstract'), 'abstract'], [ _('Article body'), 'body']])) %> | |
13 | +<% f.fields_for 'feed', @article.feed do |feed| %> | |
14 | + <%= labelled_form_field(_('Limit of posts in RSS Feed'), feed.select(:limit, ['5', '10', '20', '50'])) %> | |
15 | + <%= labelled_form_field(_('Use as description in RSS Feed:'), feed.select(:feed_item_description, [ [ _('Article abstract'), 'abstract'], [ _('Article body'), 'body']])) %> | |
16 | +<% end %> | |
16 | 17 | |
18 | +<% f.fields_for 'external_feed_builder', @article.external_feed do |efeed| %> | |
19 | + <div id='fetch-external-feed'> | |
20 | + <% enabled = @article.external_feed && @article.external_feed.enabled %> | |
21 | + <% only_once = @article.external_feed ? @article.external_feed.only_once : true %> | |
22 | + <%= labelled_check_box(_('Fetch posts from an external feed'), 'article[external_feed_builder][enabled]', 'true', enabled, {:onchange => "$('external-feed-options').toggle()"}) %> | |
23 | + <%= hidden_field_tag 'article[external_feed_builder][enabled]', 'false' %> | |
24 | + <div id='external-feed-options' style="display: <%= enabled ? 'block' : 'none' %>"> | |
25 | + <%= efeed.hidden_field(:id) %> | |
26 | + <%= labelled_form_field( _('Feed address'), efeed.text_field(:address) ) %> | |
27 | + <div id='external-feed-options-only-once'> | |
28 | + <%= labelled_radio_button( _('Fetch posts only once'), 'article[external_feed_builder][only_once]', 'true', only_once) %> | |
29 | + <%= labelled_radio_button( _('Fetch posts always'), 'article[external_feed_builder][only_once]', 'false', !only_once) %> | |
30 | + </div> | |
31 | + </div> | |
32 | + </div> | |
17 | 33 | <% end %> |
18 | 34 | |
19 | 35 | <%= javascript_tag "$('back_to').value = 'control_panel'" %> | ... | ... |
app/views/content_viewer/blog_page.rhtml
1 | +<%= content_tag('em', _('(external feed was not loaded yet)'), :id => 'external-feed-info', :class => 'metadata') if article.blog? && article.external_feed && article.external_feed.fetched_at.nil? %> | |
2 | + | |
1 | 3 | <div><%= article.body %></div> |
2 | 4 | <hr/> |
3 | 5 | <%= (children.compact.empty? ? content_tag('em', _('(no posts)')) : list_posts(user, children)) %> |
4 | - | |
5 | - | ... | ... |
app/views/content_viewer/view_page.rhtml
... | ... | @@ -97,6 +97,11 @@ |
97 | 97 | </div> |
98 | 98 | <% end %> |
99 | 99 | |
100 | +<% if ! @page.source.nil? and ! @page.source.empty?%> | |
101 | +<div id="article-source"> | |
102 | + <%= _('Source: %s') % link_to(@page.source, @page.source) %> | |
103 | +</div> | |
104 | +<% end %> | |
100 | 105 | |
101 | 106 | <div class="comments"> |
102 | 107 | <% if @page.accept_comments? %> | ... | ... |
db/migrate/063_add_published_at_and_source_to_articles.rb
0 → 100644
... | ... | @@ -0,0 +1,19 @@ |
1 | +class AddPublishedAtAndSourceToArticles < ActiveRecord::Migration | |
2 | + def self.up | |
3 | + add_column :articles, :published_at, :date | |
4 | + add_column :article_versions, :published_at, :date | |
5 | + | |
6 | + execute('UPDATE articles SET published_at = created_at WHERE published_at IS NULL') | |
7 | + execute('UPDATE article_versions SET published_at = created_at WHERE published_at IS NULL') | |
8 | + | |
9 | + add_column :articles, :source, :string, :null => true | |
10 | + add_column :article_versions, :source, :string, :null => true | |
11 | + end | |
12 | + | |
13 | + def self.down | |
14 | + remove_column :articles, :published_at | |
15 | + remove_column :article_versions, :published_at | |
16 | + remove_column :articles, :source | |
17 | + remove_column :article_versions, :source | |
18 | + end | |
19 | +end | ... | ... |
... | ... | @@ -0,0 +1,17 @@ |
1 | +class CreateExternalFeeds < ActiveRecord::Migration | |
2 | + def self.up | |
3 | + create_table :external_feeds do |t| | |
4 | + t.string :feed_title | |
5 | + t.date :fetched_at | |
6 | + t.string :address | |
7 | + t.integer :blog_id, :null => false | |
8 | + t.boolean :enabled, :null => false, :default => true | |
9 | + t.boolean :only_once, :null => false, :default => true | |
10 | + t.timestamps | |
11 | + end | |
12 | + end | |
13 | + | |
14 | + def self.down | |
15 | + drop_table :external_feeds | |
16 | + end | |
17 | +end | ... | ... |
db/schema.rb
... | ... | @@ -9,7 +9,7 @@ |
9 | 9 | # |
10 | 10 | # It's strongly recommended to check this file into your version control system. |
11 | 11 | |
12 | -ActiveRecord::Schema.define(:version => 62) do | |
12 | +ActiveRecord::Schema.define(:version => 64) do | |
13 | 13 | |
14 | 14 | create_table "article_versions", :force => true do |t| |
15 | 15 | t.integer "article_id" |
... | ... | @@ -42,6 +42,8 @@ ActiveRecord::Schema.define(:version => 62) do |
42 | 42 | t.text "setting" |
43 | 43 | t.boolean "notify_comments", :default => false |
44 | 44 | t.integer "hits", :default => 0 |
45 | + t.date "published_at" | |
46 | + t.string "source" | |
45 | 47 | end |
46 | 48 | |
47 | 49 | create_table "articles", :force => true do |t| |
... | ... | @@ -75,6 +77,8 @@ ActiveRecord::Schema.define(:version => 62) do |
75 | 77 | t.text "setting" |
76 | 78 | t.boolean "notify_comments", :default => true |
77 | 79 | t.integer "hits", :default => 0 |
80 | + t.date "published_at" | |
81 | + t.string "source" | |
78 | 82 | end |
79 | 83 | |
80 | 84 | create_table "articles_categories", :id => false, :force => true do |t| |
... | ... | @@ -160,6 +164,17 @@ ActiveRecord::Schema.define(:version => 62) do |
160 | 164 | t.string "theme" |
161 | 165 | end |
162 | 166 | |
167 | + create_table "external_feeds", :force => true do |t| | |
168 | + t.string "feed_title" | |
169 | + t.date "fetched_at" | |
170 | + t.string "address" | |
171 | + t.integer "blog_id", :null => false | |
172 | + t.boolean "enabled", :default => true, :null => false | |
173 | + t.boolean "only_once", :default => true, :null => false | |
174 | + t.datetime "created_at" | |
175 | + t.datetime "updated_at" | |
176 | + end | |
177 | + | |
163 | 178 | create_table "favorite_enteprises_people", :id => false, :force => true do |t| |
164 | 179 | t.integer "person_id" |
165 | 180 | t.integer "enterprise_id" | ... | ... |
lib/feed_handler.rb
public/stylesheets/article.css
... | ... | @@ -32,6 +32,16 @@ |
32 | 32 | font-size: 10px; |
33 | 33 | } |
34 | 34 | |
35 | +#article-source { | |
36 | + color: #999; | |
37 | + font-size: 11px; | |
38 | + text-align: right; | |
39 | + font-weight: bold; | |
40 | +} | |
41 | +#article-source a { | |
42 | + font-weight: normal; | |
43 | +} | |
44 | + | |
35 | 45 | /* * * Comments * * */ |
36 | 46 | |
37 | 47 | .comments { } |
... | ... | @@ -183,12 +193,18 @@ |
183 | 193 | margin-bottom: 2px; |
184 | 194 | } |
185 | 195 | |
196 | +.metadata, | |
186 | 197 | .blog-post .metadata { |
187 | 198 | display: block; |
188 | 199 | text-align: center; |
189 | 200 | font-size: small; |
190 | 201 | } |
191 | 202 | |
203 | +.metadata { | |
204 | + text-align: right; | |
205 | + color: gray; | |
206 | +} | |
207 | + | |
192 | 208 | #content .created-at { |
193 | 209 | color: gray; |
194 | 210 | font-size: small; |
... | ... | @@ -237,3 +253,5 @@ |
237 | 253 | text-align: center; |
238 | 254 | font-size: small; |
239 | 255 | } |
256 | + | |
257 | + | ... | ... |
public/stylesheets/controller_cms.css
... | ... | @@ -122,3 +122,26 @@ div.file-manager-button a:hover { |
122 | 122 | .file-manager-small .file-manager-controls * { |
123 | 123 | text-align: right; |
124 | 124 | } |
125 | + | |
126 | +/************* external blog options *****************/ | |
127 | + | |
128 | +#fetch-external-feed { | |
129 | + border: 1px solid #EEE; | |
130 | + padding: 5px; | |
131 | + margin: 5px 0; | |
132 | + width: 460px | |
133 | +} | |
134 | +#fetch-external-feed label, | |
135 | +#fetch-external-feed .formfield, | |
136 | +#fetch-external-feed #external-feed-options #external-feed-options-only-once label, | |
137 | +#fetch-external-feed #external-feed-options #external-feed-options-only-once .formfieldline, | |
138 | +#fetch-external-feed #external-feed-options #external-feed-options-only-once .formfield { | |
139 | + display: inline; | |
140 | +} | |
141 | +#fetch-external-feed .type-text input { | |
142 | + width: 400px; | |
143 | +} | |
144 | +#fetch-external-feed #external-feed-options label, | |
145 | +#fetch-external-feed #external-feed-options .formfield { | |
146 | + display: block; | |
147 | +} | ... | ... |
script/feed-updater
1 | 1 | #!/usr/bin/env ruby |
2 | 2 | require File.dirname(__FILE__) + '/../config/environment' |
3 | 3 | |
4 | -FeedReaderBlock.find(:all).each do |feed_block| | |
5 | - unless feed_block.address.nil? | |
4 | +(FeedReaderBlock.find(:all) + ExternalFeed.find(:all, :conditions => {:enabled => true})).each do |container| | |
5 | + unless container.address.nil? | |
6 | 6 | begin |
7 | 7 | handler = FeedHandler.new |
8 | - handler.process(feed_block) | |
9 | - RAILS_DEFAULT_LOGGER.info("%s ID %d fetched at %s" % [feed_block.class.name, feed_block.id, feed_block.fetched_at]) | |
8 | + handler.process(container) | |
9 | + RAILS_DEFAULT_LOGGER.info("%s ID %d fetched at %s" % [container.class.name, container.id, container.fetched_at]) | |
10 | 10 | rescue FeedHandler::ParseError => ex |
11 | - RAILS_DEFAULT_LOGGER.warn("Error parsing content from %s ID %d\n%s" % [feed_block.class.name, feed_block.id, ex.to_s]) | |
11 | + RAILS_DEFAULT_LOGGER.warn("Error parsing content from %s ID %d\n%s" % [container.class.name, container.id, ex.to_s]) | |
12 | 12 | rescue FeedHandler::FetchError => ex |
13 | - RAILS_DEFAULT_LOGGER.warn("Error fetching content from %s ID %d\n%s" % [feed_block.class.name, feed_block.id, ex.to_s]) | |
13 | + RAILS_DEFAULT_LOGGER.warn("Error fetching content from %s ID %d\n%s" % [container.class.name, container.id, ex.to_s]) | |
14 | 14 | rescue Exception => ex |
15 | - RAILS_DEFAULT_LOGGER.warn("Unknown error from %s ID %d\n%s" % [feed_block.class.name, feed_block.id, ex.to_s]) | |
15 | + RAILS_DEFAULT_LOGGER.warn("Unknown error from %s ID %d\n%s" % [container.class.name, container.id, ex.to_s]) | |
16 | 16 | end |
17 | 17 | end |
18 | 18 | end | ... | ... |
test/functional/cms_controller_test.rb
... | ... | @@ -716,7 +716,7 @@ class CmsControllerTest < Test::Unit::TestCase |
716 | 716 | end |
717 | 717 | |
718 | 718 | should 'offer to edit a blog' do |
719 | - profile.articles << Blog.new(:name => 'blog test') | |
719 | + profile.articles << Blog.new(:name => 'blog test', :profile => profile) | |
720 | 720 | |
721 | 721 | profile.articles.reload |
722 | 722 | assert profile.has_blog? |
... | ... | @@ -727,7 +727,7 @@ class CmsControllerTest < Test::Unit::TestCase |
727 | 727 | end |
728 | 728 | |
729 | 729 | should 'not offer to add folder to blog' do |
730 | - profile.articles << Blog.new(:name => 'blog test') | |
730 | + profile.articles << Blog.new(:name => 'blog test', :profile => profile) | |
731 | 731 | |
732 | 732 | profile.articles.reload |
733 | 733 | assert profile.has_blog? |
... | ... | @@ -737,7 +737,7 @@ class CmsControllerTest < Test::Unit::TestCase |
737 | 737 | end |
738 | 738 | |
739 | 739 | should 'not show feed subitem for blog' do |
740 | - profile.articles << Blog.new(:name => 'Blog for test') | |
740 | + profile.articles << Blog.new(:name => 'Blog for test', :profile => profile) | |
741 | 741 | |
742 | 742 | profile.articles.reload |
743 | 743 | assert profile.has_blog? |
... | ... | @@ -748,7 +748,7 @@ class CmsControllerTest < Test::Unit::TestCase |
748 | 748 | end |
749 | 749 | |
750 | 750 | should 'update feed options by edit blog form' do |
751 | - profile.articles << Blog.new(:name => 'Blog for test') | |
751 | + profile.articles << Blog.new(:name => 'Blog for test', :profile => profile) | |
752 | 752 | post :edit, :profile => profile.identifier, :id => profile.blog.id, :article => { :feed => { :limit => 7 } } |
753 | 753 | assert_equal 7, profile.blog.feed.limit |
754 | 754 | end |
... | ... | @@ -770,8 +770,9 @@ class CmsControllerTest < Test::Unit::TestCase |
770 | 770 | end |
771 | 771 | |
772 | 772 | should 'update blog posts_per_page setting' do |
773 | - profile.articles << Blog.new(:name => 'Blog for test') | |
773 | + profile.articles << Blog.new(:name => 'Blog for test', :profile => profile) | |
774 | 774 | post :edit, :profile => profile.identifier, :id => profile.blog.id, :article => { :posts_per_page => 5 } |
775 | + profile.blog.reload | |
775 | 776 | assert_equal 5, profile.blog.posts_per_page |
776 | 777 | end |
777 | 778 | |
... | ... | @@ -909,4 +910,36 @@ class CmsControllerTest < Test::Unit::TestCase |
909 | 910 | assert_template nil |
910 | 911 | assert_redirected_to file.url.merge(:view => true) |
911 | 912 | end |
913 | + | |
914 | + should 'display external feed options when edit blog' do | |
915 | + get :new, :profile => profile.identifier, :type => 'Blog' | |
916 | + assert_tag :tag => 'input', :attributes => { :name => 'article[external_feed_builder][enabled]' } | |
917 | + assert_tag :tag => 'input', :attributes => { :name => 'article[external_feed_builder][address]' } | |
918 | + end | |
919 | + | |
920 | + should "display 'Fetch posts from an external feed' checked if blog has enabled external feed" do | |
921 | + profile.articles << Blog.new(:title => 'test blog', :profile => profile) | |
922 | + profile.blog.create_external_feed(:address => 'address', :enabled => true) | |
923 | + get :edit, :profile => profile.identifier, :id => profile.blog.id | |
924 | + assert_tag :tag => 'input', :attributes => { :name => 'article[external_feed_builder][enabled]', :checked => 'checked' } | |
925 | + end | |
926 | + | |
927 | + should "display 'Fetch posts from an external feed' unchecked if blog has disabled external feed" do | |
928 | + profile.articles << Blog.new(:title => 'test blog', :profile => profile) | |
929 | + profile.blog.create_external_feed(:address => 'address', :enabled => false) | |
930 | + get :edit, :profile => profile.identifier, :id => profile.blog.id | |
931 | + assert_tag :tag => 'input', :attributes => { :name => 'article[external_feed_builder][enabled]', :checked => nil } | |
932 | + end | |
933 | + | |
934 | + should "hide external feed options when 'Fetch posts from an external feed' unchecked" do | |
935 | + get :new, :profile => profile.identifier, :type => 'Blog' | |
936 | + assert_tag :tag => 'input', :attributes => { :name => 'article[external_feed_builder][enabled]', :checked => nil } | |
937 | + assert_tag :tag => 'div', :attributes => { :id => 'external-feed-options', :style => 'display: none' } | |
938 | + end | |
939 | + | |
940 | + should 'only_once option marked by default' do | |
941 | + get :new, :profile => profile.identifier, :type => 'Blog' | |
942 | + assert_tag :tag => 'input', :attributes => { :name => 'article[external_feed_builder][only_once]', :checked => 'checked', :value => 'true' } | |
943 | + end | |
944 | + | |
912 | 945 | end | ... | ... |
test/functional/content_viewer_controller_test.rb
... | ... | @@ -14,8 +14,9 @@ class ContentViewerControllerTest < Test::Unit::TestCase |
14 | 14 | @response = ActionController::TestResponse.new |
15 | 15 | |
16 | 16 | @profile = create_user('testinguser').person |
17 | + @environment = @profile.environment | |
17 | 18 | end |
18 | - attr_reader :profile | |
19 | + attr_reader :profile, :environment | |
19 | 20 | |
20 | 21 | def test_local_files_reference |
21 | 22 | page = profile.articles.build(:name => 'test') |
... | ... | @@ -638,9 +639,9 @@ class ContentViewerControllerTest < Test::Unit::TestCase |
638 | 639 | end |
639 | 640 | |
640 | 641 | should 'extract year and month from path' do |
641 | - blog = Blog.create!(:name => 'A blog test', :profile => profile) | |
642 | - year, month = blog.created_at.year.to_s, '%02d' % blog.created_at.month | |
643 | - get :view_page, :profile => profile.identifier, :page => [blog.path, year, month] | |
642 | + profile.articles << Blog.new(:name => 'A blog test', :profile => profile) | |
643 | + year, month = profile.blog.created_at.year.to_s, '%02d' % profile.blog.created_at.month | |
644 | + get :view_page, :profile => profile.identifier, :page => [profile.blog.path, year, month] | |
644 | 645 | assert_equal({ :year => year.to_s, :month => month.to_s }, assigns(:page).filter) |
645 | 646 | end |
646 | 647 | |
... | ... | @@ -693,17 +694,17 @@ class ContentViewerControllerTest < Test::Unit::TestCase |
693 | 694 | |
694 | 695 | should 'add meta tag to rss feed on view blog' do |
695 | 696 | login_as(profile.identifier) |
696 | - a = Blog.create!(:name => 'article folder', :profile => profile) | |
697 | - get :view_page, :profile => profile.identifier, :page => [a.path] | |
698 | - assert_tag :tag => 'link', :attributes => { :rel => 'alternate', :type => 'application/rss+xml', :title => 'feed', :href => /\/#{profile.identifier}\/blog\/feed/} | |
697 | + profile.articles << Blog.new(:title => 'article blog', :profile => profile) | |
698 | + get :view_page, :profile => profile.identifier, :page => ['blog'] | |
699 | + assert_tag :tag => 'link', :attributes => { :rel => 'alternate', :type => 'application/rss+xml', :title => 'feed', :href => "http://#{environment.default_hostname}/testinguser/blog/feed" } | |
699 | 700 | end |
700 | 701 | |
701 | 702 | should 'add meta tag to rss feed on view post blog' do |
702 | 703 | login_as(profile.identifier) |
703 | - a = Blog.create!(:name => 'article folder', :profile => profile) | |
704 | - t = TextileArticle.create!(:name => 'first post', :parent => a, :profile => profile) | |
705 | - get :view_page, :profile => profile.identifier, :page => [t.path] | |
706 | - assert_tag :tag => 'link', :attributes => { :rel => 'alternate', :type => 'application/rss+xml', :title => 'feed', :href => /\/#{profile.identifier}\/blog\/feed/} | |
704 | + profile.articles << Blog.new(:name => 'article folder', :profile => profile) | |
705 | + profile.blog.posts << TextileArticle.new(:name => 'first post', :parent => profile.blog, :profile => profile) | |
706 | + get :view_page, :profile => profile.identifier, :page => ['blog', 'first-post'] | |
707 | + assert_tag :tag => 'link', :attributes => { :rel => 'alternate', :type => 'application/rss+xml', :title => 'feed', :href => "http://#{environment.default_hostname}/testinguser/blog/feed" } | |
707 | 708 | end |
708 | 709 | |
709 | 710 | should 'link to post with comment form opened' do |
... | ... | @@ -787,4 +788,16 @@ class ContentViewerControllerTest < Test::Unit::TestCase |
787 | 788 | assert_template 'slideshow' |
788 | 789 | end |
789 | 790 | |
791 | + should 'display source from article' do | |
792 | + profile.articles << TextileArticle.new(:name => "Article one", :profile => profile, :source => 'http://www.original-source.invalid') | |
793 | + get :view_page, :profile => profile.identifier, :page => ['article-one'] | |
794 | + assert_tag :tag => 'div', :attributes => { :id => 'article-source' }, :content => /http:\/\/www.original-source.invalid/ | |
795 | + end | |
796 | + | |
797 | + should 'not display source if article has no source' do | |
798 | + profile.articles << TextileArticle.new(:name => "Article one", :profile => profile) | |
799 | + get :view_page, :profile => profile.identifier, :page => ['article-one'] | |
800 | + assert_no_tag :tag => 'div', :attributes => { :id => 'article-source' } | |
801 | + end | |
802 | + | |
790 | 803 | end | ... | ... |
test/functional/profile_design_controller_test.rb
... | ... | @@ -274,7 +274,7 @@ class ProfileDesignControllerTest < Test::Unit::TestCase |
274 | 274 | end |
275 | 275 | |
276 | 276 | should 'offer to create blog archives block only if has blog' do |
277 | - Blog.create!(:name => 'Blog test', :profile => holder) | |
277 | + holder.articles << Blog.new(:name => 'Blog test', :profile => holder) | |
278 | 278 | get :add_block, :profile => 'designtestuser' |
279 | 279 | assert_tag :tag => 'input', :attributes => { :id => 'type_blogarchivesblock', :value => 'BlogArchivesBlock' } |
280 | 280 | end | ... | ... |
test/functional/profile_editor_controller_test.rb
... | ... | @@ -628,7 +628,7 @@ class ProfileEditorControllerTest < Test::Unit::TestCase |
628 | 628 | |
629 | 629 | should 'offer to config blog in control panel' do |
630 | 630 | profile.articles << Blog.new(:name => 'My blog', :profile => profile) |
631 | - get :index, :profile => 'default_user' | |
631 | + get :index, :profile => profile.identifier | |
632 | 632 | assert_tag :tag => 'a', :attributes => { :href => "/myprofile/default_user/cms/edit/#{profile.blog.id}" } |
633 | 633 | end |
634 | 634 | ... | ... |
test/unit/article_test.rb
... | ... | @@ -684,4 +684,13 @@ class ArticleTest < Test::Unit::TestCase |
684 | 684 | assert_equal profile, Article.new(:last_changed_by => nil, :profile => profile).author |
685 | 685 | end |
686 | 686 | |
687 | + should 'have published_at' do | |
688 | + assert_respond_to Article.new, :published_at | |
689 | + end | |
690 | + | |
691 | + should 'published_at is same as created_at if not set' do | |
692 | + a = Article.create!(:name => 'Published at', :profile => profile) | |
693 | + assert_equal a.created_at, a.published_at | |
694 | + end | |
695 | + | |
687 | 696 | end | ... | ... |
test/unit/blog_archives_block_test.rb
... | ... | @@ -4,7 +4,7 @@ class BlogArchivesBlockTest < ActiveSupport::TestCase |
4 | 4 | |
5 | 5 | def setup |
6 | 6 | @profile = create_user('flatline').person |
7 | - @profile.articles << Blog.new(:name => 'blog', :profile => @profile) | |
7 | + @profile.articles << Blog.new(:name => 'blog-test', :profile => @profile) | |
8 | 8 | end |
9 | 9 | attr_reader :profile |
10 | 10 | |
... | ... | @@ -22,7 +22,7 @@ class BlogArchivesBlockTest < ActiveSupport::TestCase |
22 | 22 | blog = profile.blog |
23 | 23 | for i in 1..10 do |
24 | 24 | post = TextileArticle.create!(:name => "post #{i} test", :profile => profile, :parent => blog) |
25 | - post.update_attribute(:created_at, date) | |
25 | + post.update_attribute(:published_at, date) | |
26 | 26 | end |
27 | 27 | block = BlogArchivesBlock.new |
28 | 28 | block.stubs(:owner).returns(profile) |
... | ... | @@ -34,19 +34,18 @@ class BlogArchivesBlockTest < ActiveSupport::TestCase |
34 | 34 | blog = profile.blog |
35 | 35 | for i in 1..10 do |
36 | 36 | post = TextileArticle.create!(:name => "post #{i} test", :profile => profile, :parent => blog) |
37 | - post.update_attribute(:created_at, date) | |
37 | + post.update_attribute(:published_at, date) | |
38 | 38 | end |
39 | 39 | block = BlogArchivesBlock.new |
40 | 40 | block.stubs(:owner).returns(profile) |
41 | 41 | assert_tag_in_string block.content, :tag => 'a', :content => 'January (10)', :attributes => {:href => /2008\/01/} |
42 | 42 | end |
43 | 43 | |
44 | - | |
45 | 44 | should 'order list of amount posts' do |
46 | 45 | blog = profile.blog |
47 | 46 | for i in 1..10 do |
48 | 47 | post = TextileArticle.create!(:name => "post #{i} test", :profile => profile, :parent => blog) |
49 | - post.update_attribute(:created_at, DateTime.parse("2008-#{i}-01")) | |
48 | + post.update_attribute(:published_at, DateTime.parse("2008-#{i}-01")) | |
50 | 49 | end |
51 | 50 | block = BlogArchivesBlock.new |
52 | 51 | block.stubs(:owner).returns(profile) | ... | ... |
test/unit/blog_test.rb
... | ... | @@ -38,24 +38,16 @@ class BlogTest < ActiveSupport::TestCase |
38 | 38 | assert_equal 'body', b.feed.feed_item_description |
39 | 39 | end |
40 | 40 | |
41 | - should 'get first blog from profile' do | |
42 | - p = create_user('testuser').person | |
43 | - b = Blog.create!(:profile => p, :name => 'blog_feed_test') | |
44 | - assert_equal p.blog, b | |
45 | - end | |
46 | - | |
47 | 41 | should 'save feed options' do |
48 | 42 | p = create_user('testuser').person |
49 | - b = Blog.create!(:profile => p, :name => 'blog_feed_test') | |
43 | + p.articles << Blog.new(:profile => p, :name => 'blog_feed_test') | |
50 | 44 | p.blog.feed = { :limit => 7 } |
51 | 45 | assert_equal 7, p.blog.feed.limit |
52 | 46 | end |
53 | 47 | |
54 | 48 | should 'save feed options after create blog' do |
55 | 49 | p = create_user('testuser').person |
56 | - b = Blog.create!(:profile => p, :name => 'blog_feed_test', :feed => { :limit => 7 }) | |
57 | - | |
58 | - p.blog.feed.reload | |
50 | + p.articles << Blog.new(:profile => p, :name => 'blog_feed_test', :feed => { :limit => 7 }) | |
59 | 51 | assert_equal 7, p.blog.feed.limit |
60 | 52 | end |
61 | 53 | |
... | ... | @@ -65,16 +57,16 @@ class BlogTest < ActiveSupport::TestCase |
65 | 57 | end |
66 | 58 | |
67 | 59 | should 'update posts per page setting' do |
68 | - p = create_user('testusermerda').person | |
69 | - blog = Blog.create!(:profile => p, :name => 'Blog test') | |
70 | - blog.reload | |
71 | - blog.posts_per_page = 5 | |
60 | + p = create_user('testuser').person | |
61 | + p.articles << Blog.new(:profile => p, :name => 'Blog test') | |
62 | + blog = p.blog | |
63 | + blog.posts_per_page = 7 | |
72 | 64 | assert blog.save! |
73 | - assert_equal 5, blog.posts_per_page | |
65 | + assert_equal 7, p.blog.posts_per_page | |
74 | 66 | end |
75 | 67 | |
76 | 68 | should 'has posts' do |
77 | - p = create_user('testusermerda').person | |
69 | + p = create_user('testuser').person | |
78 | 70 | blog = Blog.create!(:profile => p, :name => 'Blog test') |
79 | 71 | post = TextileArticle.create!(:name => 'First post', :profile => p, :parent => blog) |
80 | 72 | blog.children << post |
... | ... | @@ -82,25 +74,56 @@ class BlogTest < ActiveSupport::TestCase |
82 | 74 | end |
83 | 75 | |
84 | 76 | should 'not includes rss feed in posts' do |
85 | - p = create_user('testusermerda').person | |
77 | + p = create_user('testuser').person | |
86 | 78 | blog = Blog.create!(:profile => p, :name => 'Blog test') |
87 | 79 | assert_includes blog.children, blog.feed |
88 | 80 | assert_not_includes blog.posts, blog.feed |
89 | 81 | end |
90 | 82 | |
91 | - should 'list posts ordered by created at' do | |
92 | - p = create_user('testusermerda').person | |
83 | + should 'list posts ordered by published at' do | |
84 | + p = create_user('testuser').person | |
93 | 85 | blog = Blog.create!(:profile => p, :name => 'Blog test') |
94 | 86 | newer = TextileArticle.create!(:name => 'Post 2', :parent => blog, :profile => p) |
95 | - older = TextileArticle.create!(:name => 'Post 1', :parent => blog, :profile => p, :created_at => Time.now - 1.month) | |
87 | + older = TextileArticle.create!(:name => 'Post 1', :parent => blog, :profile => p, :published_at => Time.now - 1.month) | |
96 | 88 | assert_equal [newer, older], blog.posts |
97 | 89 | end |
98 | 90 | |
99 | 91 | should 'has filter' do |
100 | - p = create_user('testusermerda').person | |
92 | + p = create_user('testuser').person | |
101 | 93 | blog = Blog.create!(:profile => p, :name => 'Blog test') |
102 | 94 | blog.filter = {:param => 'value'} |
103 | 95 | assert_equal 'value', blog.filter[:param] |
104 | 96 | end |
105 | 97 | |
98 | + should 'has one external feed' do | |
99 | + p = create_user('testuser').person | |
100 | + blog = Blog.create!(:profile => p, :name => 'Blog test') | |
101 | + efeed = blog.create_external_feed(:address => 'http://invalid.url') | |
102 | + assert_equal efeed, blog.external_feed | |
103 | + end | |
104 | + | |
105 | + should 'build external feed after save' do | |
106 | + p = create_user('testuser').person | |
107 | + blog = Blog.new(:profile => p, :name => 'Blog test') | |
108 | + blog.external_feed_builder = { :address => 'feed address' } | |
109 | + blog.save! | |
110 | + assert blog.external_feed.valid? | |
111 | + end | |
112 | + | |
113 | + should 'update external feed' do | |
114 | + p = create_user('testuser').person | |
115 | + blog = Blog.new(:profile => p, :name => 'Blog test') | |
116 | + blog.create_external_feed(:address => 'feed address') | |
117 | + blog.external_feed_builder = { :address => 'address edited' } | |
118 | + blog.save! | |
119 | + assert_equal 'address edited', blog.external_feed.address | |
120 | + end | |
121 | + | |
122 | + should 'invalid blog if has invalid external_feed' do | |
123 | + p = create_user('testuser').person | |
124 | + blog = Blog.new(:profile => p, :name => 'Blog test', :external_feed_builder => {:enabled => true}) | |
125 | + blog.save | |
126 | + assert ! blog.valid? | |
127 | + end | |
128 | + | |
106 | 129 | end | ... | ... |
test/unit/content_viewer_helper_test.rb
... | ... | @@ -11,17 +11,17 @@ class ContentViewerHelperTest < Test::Unit::TestCase |
11 | 11 | end |
12 | 12 | attr :profile |
13 | 13 | |
14 | - should 'display created-at for blog posts' do | |
14 | + should 'display published-at for blog posts' do | |
15 | 15 | blog = Blog.create!(:name => 'Blog test', :profile => profile) |
16 | 16 | post = TextileArticle.create!(:name => 'post test', :profile => profile, :parent => blog) |
17 | 17 | result = article_title(post) |
18 | - assert_match /#{show_date(post.created_at)}, by .*#{profile.identifier}/, result | |
18 | + assert_match /#{show_date(post.published_at)}, by .*#{profile.identifier}/, result | |
19 | 19 | end |
20 | 20 | |
21 | - should 'not display created-at for non-blog posts' do | |
21 | + should 'not display published-at for non-blog posts' do | |
22 | 22 | article = TextileArticle.create!(:name => 'article for test', :profile => profile) |
23 | 23 | result = article_title(article) |
24 | - assert_no_match /#{show_date(article.created_at)}, by .*#{profile.identifier}/, result | |
24 | + assert_no_match /#{show_date(article.published_at)}, by .*#{profile.identifier}/, result | |
25 | 25 | end |
26 | 26 | |
27 | 27 | should 'create link on title of blog posts' do |
... | ... | @@ -54,7 +54,7 @@ class ContentViewerHelperTest < Test::Unit::TestCase |
54 | 54 | end |
55 | 55 | |
56 | 56 | should 'not list feed article' do |
57 | - profile.articles << Blog.new(:name => 'Blog test') | |
57 | + profile.articles << Blog.new(:name => 'Blog test', :profile => profile) | |
58 | 58 | assert_includes profile.blog.children.map{|i| i.class}, RssFeed |
59 | 59 | result = list_posts(nil, profile.blog.posts) |
60 | 60 | assert_no_match /feed/, result |
... | ... | @@ -64,10 +64,10 @@ class ContentViewerHelperTest < Test::Unit::TestCase |
64 | 64 | blog = Blog.create!(:name => 'Blog test', :profile => profile) |
65 | 65 | |
66 | 66 | nov = TextileArticle.create!(:name => 'November post', :parent => blog, :profile => profile) |
67 | - nov.update_attributes!(:created_at => DateTime.parse('2008-11-15')) | |
67 | + nov.update_attributes!(:published_at => DateTime.parse('2008-11-15')) | |
68 | 68 | |
69 | 69 | sep = TextileArticle.create!(:name => 'September post', :parent => blog, :profile => profile) |
70 | - sep.update_attribute(:created_at, DateTime.parse('2008-09-10')) | |
70 | + sep.update_attribute(:published_at, DateTime.parse('2008-09-10')) | |
71 | 71 | |
72 | 72 | blog.reload |
73 | 73 | blog.filter = {:year => 2008, :month => 11} | ... | ... |
... | ... | @@ -0,0 +1,63 @@ |
1 | +require File.dirname(__FILE__) + '/../test_helper' | |
2 | + | |
3 | +class ExternalFeedTest < ActiveSupport::TestCase | |
4 | + | |
5 | + def setup | |
6 | + @profile = create_user('test-person').person | |
7 | + @blog = Blog.create!(:name => 'test-blog', :profile => @profile) | |
8 | + end | |
9 | + attr_reader :profile, :blog | |
10 | + | |
11 | + should 'require blog' do | |
12 | + e = ExternalFeed.new(:address => 'http://localhost') | |
13 | + assert !e.valid? | |
14 | + e.blog = blog | |
15 | + assert e.save! | |
16 | + end | |
17 | + | |
18 | + should 'belongs to blog' do | |
19 | + e = ExternalFeed.create!(:address => 'http://localhost', :blog => blog) | |
20 | + e.reload | |
21 | + assert_equal blog, e.blog | |
22 | + end | |
23 | + | |
24 | + should 'not add same item twice' do | |
25 | + e = ExternalFeed.create!(:address => 'http://localhost', :blog => blog) | |
26 | + assert e.add_item('Article title', 'http://orig.link.invalid', Time.now, 'Content for external post') | |
27 | + assert !e.add_item('Article title', 'http://orig.link.invalid', Time.now, 'Content for external post') | |
28 | + assert_equal 1, e.blog.posts.size | |
29 | + end | |
30 | + | |
31 | + should 'nothing when clear' do | |
32 | + assert_respond_to ExternalFeed.new, :clear | |
33 | + end | |
34 | + | |
35 | + should 'not limit' do | |
36 | + assert_equal 0, ExternalFeed.new.limit | |
37 | + end | |
38 | + | |
39 | + should 'disable external feed if fetch only once on finish fetch' do | |
40 | + e = ExternalFeed.create(:address => 'http://localhost', :blog => blog, :only_once => true, :enabled => true) | |
41 | + assert e.enabled | |
42 | + assert e.finish_fetch | |
43 | + assert !e.enabled | |
44 | + end | |
45 | + | |
46 | + should 'add items to blog as posts' do | |
47 | + handler = FeedHandler.new | |
48 | + e = ExternalFeed.create!(:address => 'test/fixtures/files/feed.xml', :blog => blog, :enabled => true) | |
49 | + handler.process(e) | |
50 | + assert_equal ["Last POST", "Second POST", "First POST"], e.blog.posts.map{|i| i.title} | |
51 | + end | |
52 | + | |
53 | + should 'require address if enabled' do | |
54 | + e = ExternalFeed.new(:blog => blog, :enabled => true) | |
55 | + assert !e.valid? | |
56 | + end | |
57 | + | |
58 | + should 'not require address if disabled' do | |
59 | + e = ExternalFeed.new(:blog => blog, :enabled => false) | |
60 | + assert e.valid? | |
61 | + end | |
62 | + | |
63 | +end | ... | ... |
test/unit/feed_handler_test.rb
... | ... | @@ -68,8 +68,8 @@ class FeedHandlerTest < Test::Unit::TestCase |
68 | 68 | handler.process(container) |
69 | 69 | end |
70 | 70 | |
71 | - should 'save after processing' do | |
72 | - container.expects(:save!) | |
71 | + should 'finish_fetch after processing' do | |
72 | + container.expects(:finish_fetch) | |
73 | 73 | handler.process(container) |
74 | 74 | end |
75 | 75 | ... | ... |
test/unit/feed_reader_block_test.rb
test/unit/profile_test.rb
... | ... | @@ -1160,7 +1160,7 @@ class ProfileTest < Test::Unit::TestCase |
1160 | 1160 | |
1161 | 1161 | should 'has blog' do |
1162 | 1162 | p = create_user('testuser').person |
1163 | - b = Blog.create!(:profile => p, :name => 'blog_feed_test') | |
1163 | + p.articles << Blog.new(:profile => p, :name => 'blog_feed_test') | |
1164 | 1164 | assert p.has_blog? |
1165 | 1165 | end |
1166 | 1166 | |
... | ... | @@ -1169,12 +1169,6 @@ class ProfileTest < Test::Unit::TestCase |
1169 | 1169 | assert !p.has_blog? |
1170 | 1170 | end |
1171 | 1171 | |
1172 | - should 'get blog when has blog' do | |
1173 | - p = create_user('testuser').person | |
1174 | - b = Blog.create!(:profile => p, :name => 'blog_feed_test') | |
1175 | - assert_equal b, p.blog | |
1176 | - end | |
1177 | - | |
1178 | 1172 | should 'get nil when no blog' do |
1179 | 1173 | p = create_user('testuser').person |
1180 | 1174 | assert_nil p.blog | ... | ... |
test/unit/published_article_test.rb
... | ... | @@ -56,10 +56,10 @@ class PublishedArticleTest < ActiveSupport::TestCase |
56 | 56 | @article.expects(:parent).returns(parent) |
57 | 57 | parent.expects(:blog?).returns(true) |
58 | 58 | prof = Community.create!(:name => 'test_comm', :identifier => 'test_comm') |
59 | - blog = Blog.create!(:profile => prof, :name => 'Blog test') | |
59 | + prof.articles << Blog.new(:profile => prof, :name => 'Blog test') | |
60 | 60 | p = PublishedArticle.create!(:reference_article => @article, :profile => prof) |
61 | 61 | |
62 | - assert_equal p.parent, blog | |
62 | + assert_equal p.parent, prof.blog | |
63 | 63 | end |
64 | 64 | |
65 | 65 | should 'not be created in community blog if did not come from a blog' do | ... | ... |
test/unit/rss_feed_test.rb
... | ... | @@ -109,7 +109,7 @@ class RssFeedTest < Test::Unit::TestCase |
109 | 109 | |
110 | 110 | should 'list blog posts with more recent first and respecting limit' do |
111 | 111 | profile = create_user('testuser').person |
112 | - blog = Blog.create(:name => 'blog', :profile => profile) | |
112 | + blog = Blog.create!(:name => 'blog-test', :profile => profile) | |
113 | 113 | posts = [] |
114 | 114 | 6.times do |i| |
115 | 115 | posts << TextArticle.create!(:name => "post #{i}", :profile => profile, :parent => blog) |
... | ... | @@ -123,7 +123,7 @@ class RssFeedTest < Test::Unit::TestCase |
123 | 123 | |
124 | 124 | should 'list only published posts from blog' do |
125 | 125 | profile = create_user('testuser').person |
126 | - blog = Blog.create(:name => 'blog', :profile => profile) | |
126 | + blog = Blog.create!(:name => 'blog-test', :profile => profile) | |
127 | 127 | posts = [] |
128 | 128 | 5.times do |i| |
129 | 129 | posts << TextArticle.create!(:name => "post #{i}", :profile => profile, :parent => blog) | ... | ... |
... | ... | @@ -0,0 +1,9 @@ |
1 | +# monkey patch to remove a single error from the errors collection | |
2 | +# http://dev.rubyonrails.org/ticket/8137 | |
3 | + | |
4 | +ActiveRecord::Errors.module_eval do | |
5 | + # remove a single error from the errors collection by key | |
6 | + def delete(key) | |
7 | + @errors.delete(key.to_s) | |
8 | + end | |
9 | +end | ... | ... |