radiant-comments-extension 0.0.6 → 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -1
- data/CHANGELOG +6 -0
- data/Gemfile +4 -0
- data/README.rdoc +9 -0
- data/Rakefile +0 -16
- data/TODO +0 -1
- data/app/controllers/admin/comments_controller.rb +1 -5
- data/app/controllers/comments_controller.rb +46 -4
- data/app/models/akismet_spam_filter.rb +1 -1
- data/app/models/comment.rb +7 -1
- data/app/models/mollom_spam_filter.rb +17 -7
- data/app/models/spam_filter.rb +1 -0
- data/app/views/admin/comments/_comment.html.haml +26 -0
- data/app/views/admin/comments/_form.html.haml +37 -0
- data/app/views/admin/comments/edit.html.haml +4 -0
- data/app/views/admin/comments/index.html.haml +57 -0
- data/app/views/admin/pages/{_comments.rhtml → _comments.html.haml} +0 -0
- data/app/views/admin/pages/_edit_comments_enabled.html.haml +7 -0
- data/app/views/admin/pages/_index_head_view_comments.html.haml +1 -0
- data/app/views/admin/pages/_index_view_comments.html.haml +7 -0
- data/comments_extension.rb +11 -27
- data/config/locales/en.yml +26 -0
- data/config/locales/nl.yml +26 -0
- data/config/routes.rb +16 -0
- data/db/migrate/002_create_snippets.rb +4 -4
- data/lib/comment_page_extensions.rb +15 -2
- data/lib/comment_tags.rb +55 -0
- data/lib/radiant-comments-extension.rb +2 -0
- data/lib/radiant-comments-extension/version.rb +3 -0
- data/public/stylesheets/admin/comments.css +21 -29
- data/radiant-comments-extension.gemspec +8 -117
- data/spec/controllers/page_postback_spec.rb +2 -1
- data/spec/models/comment_spec.rb +7 -23
- data/spec/models/mollom_spam_filter_spec.rb +5 -0
- metadata +30 -32
- data/VERSION +0 -1
- data/app/views/admin/comments/_comment.rhtml +0 -34
- data/app/views/admin/comments/_form.rhtml +0 -36
- data/app/views/admin/comments/edit.rhtml +0 -5
- data/app/views/admin/comments/index.rhtml +0 -55
- data/app/views/admin/pages/_edit_comments_enabled.rhtml +0 -8
- data/app/views/admin/pages/_index_head_view_comments.rhtml +0 -1
- data/app/views/admin/pages/_index_view_comments.rhtml +0 -11
- data/app/views/comments/_comment.rhtml +0 -1
- data/app/views/comments/_form.rhtml +0 -23
- data/app/views/comments/_new.rhtml +0 -5
- data/lib/mollom.rb +0 -246
data/.gitignore
CHANGED
data/CHANGELOG
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
= Git
|
2
2
|
|
3
|
+
= 0.0.7
|
4
|
+
|
5
|
+
* Updated to use of the Mollom gem i.o. local copy [Benny Degezelle]
|
6
|
+
* Updated to use mollom to it's full potential (show CAPTCHA when unsure) [Benny Degezelle]
|
7
|
+
* Added r:comments:spam_message which only expands if a posted comment is thought to be spam [Benny Degezelle]
|
8
|
+
|
3
9
|
= 0.0.6
|
4
10
|
|
5
11
|
* Added spam_answer_tag for simple spam protection or CAPTCHA
|
data/Gemfile
ADDED
data/README.rdoc
CHANGED
@@ -15,6 +15,7 @@ If you want to contribute features or fixes please write your specs and code and
|
|
15
15
|
* Requires will_paginate Rails plugin (http://github.com/mislav/will_paginate/tree/master)
|
16
16
|
* Requires fastercsv 1.2.3 or greater.
|
17
17
|
* Requires sanitize 1.0.8 or greater.
|
18
|
+
* Requires mollom 0.2.2 or greater.
|
18
19
|
|
19
20
|
== Installation
|
20
21
|
|
@@ -24,6 +25,14 @@ To install Comments, run:
|
|
24
25
|
|
25
26
|
This _WILL_ delete any snippets named 'comment', 'comments' and 'comment_form' if these exist.
|
26
27
|
|
28
|
+
If you are upgrading from a previous version with out the sanitizer run:
|
29
|
+
|
30
|
+
rake radiant:extensions:comments:initialize
|
31
|
+
|
32
|
+
or if you want to clean up your initializer for the sanitize gem run:
|
33
|
+
|
34
|
+
rake radiant:extensions:comments:forced_initialize
|
35
|
+
|
27
36
|
== More Help
|
28
37
|
|
29
38
|
See the included HELP docs here or read them in your Radiant interface with the Help extension installed.
|
data/Rakefile
CHANGED
@@ -1,19 +1,3 @@
|
|
1
|
-
begin
|
2
|
-
require 'jeweler'
|
3
|
-
Jeweler::Tasks.new do |gem|
|
4
|
-
gem.name = "radiant-comments-extension"
|
5
|
-
gem.summary = %Q{Comments Extension for Radiant CMS}
|
6
|
-
gem.description = %Q{Adds blog-like comment functionality to Radiant.}
|
7
|
-
gem.email = "jim@saturnflyer.com"
|
8
|
-
gem.homepage = "http://github.com/saturnflyer/radiant-comments-extension"
|
9
|
-
gem.authors = ['Jim Gay', 'Ryan Heneise', 'Sean Cribbs','John Muhl', 'Sven Schwyn', 'Gerrit Kaiser', 'Stephen Lombardo', 'Benny Degezelle', 'Frank Louwers', 'Michael Hale', 'Nathaniel Talbott', 'John Croisant', 'Jon Leighton', 'Witter Cheng', 'Keith Bingman']
|
10
|
-
gem.add_development_dependency "radiant"
|
11
|
-
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
12
|
-
end
|
13
|
-
rescue LoadError
|
14
|
-
puts "Jeweler (or a dependency) not available. This is only required if you plan to package dashboard as a gem."
|
15
|
-
end
|
16
|
-
|
17
1
|
# Determine where the RSpec plugin is by loading the boot
|
18
2
|
unless defined? RADIANT_ROOT
|
19
3
|
ENV["RAILS_ENV"] = "test"
|
data/TODO
CHANGED
@@ -114,11 +114,7 @@ class Admin::CommentsController < ApplicationController
|
|
114
114
|
end
|
115
115
|
|
116
116
|
def clear_cache
|
117
|
-
|
118
|
-
ResponseCache.instance.clear
|
119
|
-
else
|
120
|
-
Radiant::Cache.clear
|
121
|
-
end
|
117
|
+
Radiant::Cache.clear
|
122
118
|
end
|
123
119
|
|
124
120
|
def clear_single_page_cache(comment)
|
@@ -24,13 +24,42 @@ class CommentsController < ApplicationController
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
-
|
28
|
-
|
27
|
+
if comment.approved?
|
28
|
+
redirect_to "#{@page.url}comments#comment-#{comment.id}"
|
29
|
+
elsif Comment.spam_filter == MollomSpamFilter && MollomSpamFilter.mollom_response(comment).to_s == 'unsure'
|
30
|
+
set_up_page_with_captcha(comment)
|
31
|
+
render :text => @page.render and return
|
32
|
+
else
|
33
|
+
@page.posted_comment_is_spam = true
|
34
|
+
#comment.destroy
|
35
|
+
render :text => @page.render
|
36
|
+
end
|
29
37
|
rescue ActiveRecord::RecordInvalid
|
38
|
+
if comment.errors.on(:spam_answer)
|
39
|
+
@page.posted_comment_is_spam = true
|
40
|
+
end
|
30
41
|
@page.last_comment = comment
|
31
42
|
render :text => @page.render
|
32
|
-
|
33
|
-
|
43
|
+
rescue SpamFilter::Spam
|
44
|
+
@page.posted_comment_is_spam = true
|
45
|
+
#comment.destroy
|
46
|
+
render :text => @page.render
|
47
|
+
end
|
48
|
+
|
49
|
+
def solve_captcha
|
50
|
+
comment = Comment.find_by_mollom_id(params[:comment_mollom_id])
|
51
|
+
answer = params[:captcha_answer]
|
52
|
+
mollom = Comment.spam_filter.mollom
|
53
|
+
if mollom.valid_captcha? :session_id => comment.mollom_id, :solution => answer
|
54
|
+
comment.approve!
|
55
|
+
redirect_to "#{@page.url}comments#comment-#{comment.id}"
|
56
|
+
else
|
57
|
+
set_up_page_with_captcha(comment)
|
58
|
+
render :text => @page.render
|
59
|
+
end
|
60
|
+
rescue Mollom::Error # when you don't provide 'correct' or 'incorrect' in mollom developer mode
|
61
|
+
set_up_page_with_captcha(comment)
|
62
|
+
render :text => @page.render
|
34
63
|
end
|
35
64
|
|
36
65
|
private
|
@@ -55,5 +84,18 @@ class CommentsController < ApplicationController
|
|
55
84
|
end
|
56
85
|
end
|
57
86
|
end
|
87
|
+
|
88
|
+
def set_up_page_with_captcha(comment)
|
89
|
+
mollom = Comment.spam_filter.mollom
|
90
|
+
if comment.mollom_id.nil?
|
91
|
+
captcha = mollom.image_captcha
|
92
|
+
comment.update_attribute(:mollom_id, captcha['session_id'])
|
93
|
+
else
|
94
|
+
captcha = mollom.image_captcha(:session_id => comment.mollom_id)
|
95
|
+
end
|
96
|
+
@page.last_comment = comment
|
97
|
+
@page.captcha_url = captcha['url']
|
98
|
+
@page.comment_mollom_id = @page.captcha_url[/\/([a-z0-9]*).png/, 1]
|
99
|
+
end
|
58
100
|
|
59
101
|
end
|
@@ -10,7 +10,7 @@ class AkismetSpamFilter < SpamFilter
|
|
10
10
|
|
11
11
|
def approved?(comment)
|
12
12
|
(akismet.valid? && ham?(comment)) || raise(SpamFilter::Spam)
|
13
|
-
rescue
|
13
|
+
rescue StandardError, TimeoutError
|
14
14
|
# Spam and anything raised by Net::HTTP, e.g. Errno, Timeout stuff
|
15
15
|
false
|
16
16
|
end
|
data/app/models/comment.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'digest/md5'
|
2
|
+
require 'sanitize'
|
2
3
|
class Comment < ActiveRecord::Base
|
3
4
|
belongs_to :page, :counter_cache => true
|
4
5
|
|
@@ -35,8 +36,12 @@ class Comment < ActiveRecord::Base
|
|
35
36
|
self.referrer = request.env['HTTP_REFERER']
|
36
37
|
end
|
37
38
|
|
39
|
+
def is_ham?
|
40
|
+
spam_filter.valid?(self)
|
41
|
+
end
|
42
|
+
|
38
43
|
def auto_approve?
|
39
|
-
Radiant::Config['comments.auto_approve'] == "true" &&
|
44
|
+
Radiant::Config['comments.auto_approve'] == "true" && is_ham?
|
40
45
|
end
|
41
46
|
|
42
47
|
def unapproved?
|
@@ -75,6 +80,7 @@ class Comment < ActiveRecord::Base
|
|
75
80
|
|
76
81
|
def auto_approve
|
77
82
|
self.approved_at = Time.now if auto_approve?
|
83
|
+
true
|
78
84
|
end
|
79
85
|
|
80
86
|
def apply_filter
|
@@ -8,9 +8,14 @@ class MollomSpamFilter < SpamFilter
|
|
8
8
|
!Radiant::Config['comments.mollom_publickey'].blank?
|
9
9
|
end
|
10
10
|
|
11
|
+
def valid?(comment)
|
12
|
+
approved?(comment)
|
13
|
+
rescue SpamFilter::Unsure
|
14
|
+
end
|
15
|
+
|
11
16
|
def approved?(comment)
|
12
17
|
(mollom.key_ok? && ham?(comment)) || raise(SpamFilter::Spam)
|
13
|
-
rescue Mollom::Error, SpamFilter::Spam
|
18
|
+
rescue Mollom::Error, SpamFilter::Spam, TimeoutError
|
14
19
|
false
|
15
20
|
end
|
16
21
|
|
@@ -32,19 +37,24 @@ class MollomSpamFilter < SpamFilter
|
|
32
37
|
end
|
33
38
|
end
|
34
39
|
|
35
|
-
|
36
|
-
|
37
|
-
response = mollom.check_content(
|
40
|
+
def mollom_response(comment)
|
41
|
+
mollom.check_content(
|
38
42
|
:author_name => comment.author, # author name
|
39
43
|
:author_mail => comment.author_email, # author email
|
40
44
|
:author_url => comment.author_url, # author url
|
41
|
-
:post_body => comment.content # comment text
|
45
|
+
:post_body => comment.content, # comment text
|
46
|
+
:author_ip => comment.author_ip
|
42
47
|
)
|
43
|
-
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def ham?(comment)
|
53
|
+
response = mollom_response(comment)
|
44
54
|
save_mollom_servers
|
45
55
|
response.ham?
|
46
56
|
end
|
47
|
-
|
57
|
+
|
48
58
|
def save_mollom_servers
|
49
59
|
Rails.cache.write('MOLLOM_SERVER_CACHE', mollom.server_list.to_yaml) if mollom.key_ok?
|
50
60
|
rescue Mollom::Error #TODO: something with this error...
|
data/app/models/spam_filter.rb
CHANGED
@@ -0,0 +1,26 @@
|
|
1
|
+
%tr{:id => dom_id(comment), :class => "comment comment-#{comment.ap_status}"}
|
2
|
+
%td.content{:title => comment.content.size >= 70 ? t('click_to_toggle') : "" }
|
3
|
+
%blockquote.short= escape_once(truncate(comment.content, :length => 70))
|
4
|
+
- if comment.content.size >= 70
|
5
|
+
%blockquote.expanded{:style => "display:none"}
|
6
|
+
= escape_once(comment.content)
|
7
|
+
%td.date-column
|
8
|
+
= comment.created_at.strftime("%b %e, %Y at %I:%M%p")
|
9
|
+
%td.author-column
|
10
|
+
= escape_once(comment.author)
|
11
|
+
- unless comment.author_email.blank?
|
12
|
+
= mail_to(comment.author_email, image_tag("admin/email.png"))
|
13
|
+
- unless comment.author_url.blank?
|
14
|
+
= link_to(image_tag("admin/link.png"), comment.author_url)
|
15
|
+
- unless @page
|
16
|
+
%td.page-column
|
17
|
+
= link_to truncate(comment.page.title, :length => 40), comment.page.url, :class => 'view-page'
|
18
|
+
= link_to image_tag("admin/page_white_edit.png"), edit_admin_page_path(comment.page), :title => t('edit_page')
|
19
|
+
%td.controls-column
|
20
|
+
- if comment.approved?
|
21
|
+
= link_to(image_tag('admin/error.png'), unapprove_admin_comment_path(comment), :method => :put, :title => t('unapprove_comment'))
|
22
|
+
- else
|
23
|
+
= link_to(image_tag('admin/accept.png'), approve_admin_comment_path(comment), :title => t('approve_comment'), :method => :put)
|
24
|
+
|
25
|
+
= link_to image_tag("admin/delete.png"), admin_comment_path(comment), :method => :delete, :confirm => t('are_you_sure_you_want_to_delete_this_comment'), :title => t('delete_comment')
|
26
|
+
= link_to image_tag("admin/comment_edit.png"), edit_admin_comment_path(comment), :title => t('edit_comment')
|
@@ -0,0 +1,37 @@
|
|
1
|
+
- if Comment.spam_filter == SimpleSpamFilter
|
2
|
+
%div{:style => "display:none"}
|
3
|
+
= f.hidden_field :spam_answer, :value => "hemidemisemiquaver"
|
4
|
+
= f.hidden_field :valid_spam_answer, :value => Digest::MD5.hexdigest("hemidemisemiquaver")
|
5
|
+
|
6
|
+
#comment_form_container.form-area
|
7
|
+
.set
|
8
|
+
%p.title
|
9
|
+
= f.label :author, t('author')
|
10
|
+
= f.text_field :author
|
11
|
+
%p
|
12
|
+
= f.label :author_url, t('url')
|
13
|
+
= f.text_field :author_url
|
14
|
+
%p
|
15
|
+
= f.label :author_email, t('email')
|
16
|
+
= f.text_field :author_email
|
17
|
+
%p
|
18
|
+
%label{:for => "comment_content"}= t('comment')
|
19
|
+
%br/
|
20
|
+
= f.text_area :content, :rows => 10, :cols => 72, :class => "textarea"
|
21
|
+
%p
|
22
|
+
%label{:for => "comment_filter_id"}= t('filter')
|
23
|
+
= f.select :filter_id, [['<none>', '']] + TextFilter.descendants.map { |s| s.filter_name }.sort
|
24
|
+
%p
|
25
|
+
Referred by:
|
26
|
+
= f.object.referrer
|
27
|
+
%p
|
28
|
+
Browser:
|
29
|
+
= f.object.user_agent
|
30
|
+
%p
|
31
|
+
Author's IP Address:
|
32
|
+
= f.object.author_ip
|
33
|
+
|
34
|
+
%p.buttons
|
35
|
+
= save_model_button(@comment)
|
36
|
+
= t('or')
|
37
|
+
= link_to t('cancel'), :back
|
@@ -0,0 +1,57 @@
|
|
1
|
+
- content_for :page_scripts do
|
2
|
+
:plain
|
3
|
+
document.observe('dom:loaded', function(){
|
4
|
+
Event.addBehavior({
|
5
|
+
'tr.comment td.content': function(event){
|
6
|
+
$(this).observe('click', function(event){
|
7
|
+
if($(this).down('blockquote.expanded')){
|
8
|
+
$(this).down('blockquote.expanded').toggle();
|
9
|
+
$(this).down('blockquote.short').toggle();
|
10
|
+
}
|
11
|
+
event.stop();
|
12
|
+
});
|
13
|
+
}
|
14
|
+
});
|
15
|
+
});
|
16
|
+
|
17
|
+
- include_stylesheet 'admin/comments'
|
18
|
+
|
19
|
+
.outset
|
20
|
+
#filters
|
21
|
+
%ul.comment-nav
|
22
|
+
- if @page
|
23
|
+
%li
|
24
|
+
= "#{@page.comments.count.to_s} #{t("comment_states.#{params[:status]}")} " if params[:status]
|
25
|
+
= "#{pluralize(@page.comments.count, 'Comment')} on #{link_to @page.title, edit_admin_page_path(@page)}"
|
26
|
+
%li.all
|
27
|
+
= link_or_span_unless_current(t('comment_states.all'), :status => 'all', :page_id => params[:page_id])
|
28
|
+
%li.approved
|
29
|
+
= link_or_span_unless_current(t('comment_states.approved'), :status => "approved", :page_id => params[:page_id])
|
30
|
+
%li.unapproved
|
31
|
+
= link_or_span_unless_current(t('comment_states.unapproved'), :status => "unapproved", :page_id => params[:page_id])
|
32
|
+
|
33
|
+
|
34
|
+
#comments_table
|
35
|
+
%table#comments.index
|
36
|
+
%thead
|
37
|
+
%tr
|
38
|
+
%th= t('content')
|
39
|
+
%th= t('date_string')
|
40
|
+
%th= t('author')
|
41
|
+
- unless @page
|
42
|
+
%th= t('page')
|
43
|
+
%th= t('actions')
|
44
|
+
%tbody
|
45
|
+
= render(:partial => "comment", :collection => @comments) || %Q[<tr><td class="note" colspan="#{@page ? 4 : 5}">No comments</td></tr>]
|
46
|
+
%p
|
47
|
+
%small.notice
|
48
|
+
= Comment.spam_filter.message
|
49
|
+
|
50
|
+
|
51
|
+
#actions
|
52
|
+
= will_paginate @comments
|
53
|
+
%ul
|
54
|
+
%li
|
55
|
+
= link_to "#{image('table_save')} #{t('download_csv')}", :format => :csv
|
56
|
+
%li
|
57
|
+
= link_to "#{image('comments_delete')} #{t('delete_unapproved')}", destroy_unapproved_admin_comments_url, :class => 'delete-unapproved', :method => :delete, :confirm => 'Is it OK to delete ALL Unapproved Comments?'
|
File without changes
|
@@ -0,0 +1,7 @@
|
|
1
|
+
%p{:style => "clear:left"}
|
2
|
+
%label{:for => "page_enable_comments"}
|
3
|
+
= check_box "page", "enable_comments"
|
4
|
+
= t('allow_comments_on_page')
|
5
|
+
- unless @page.new_record? or @page.comments_count < 1
|
6
|
+
%small
|
7
|
+
= link_to t('currently_x_view_comments', :count => @page.comments_count), admin_page_comments_path(:page_id => @page.id)
|
@@ -0,0 +1 @@
|
|
1
|
+
%th.comments= t('comments')
|
@@ -0,0 +1,7 @@
|
|
1
|
+
%td.comment_link
|
2
|
+
- if page.respond_to?('enable_comments')
|
3
|
+
%small
|
4
|
+
- if page.enable_comments
|
5
|
+
= link_to "#{page.comments_count} comments", admin_page_comments_path(:page_id => page.id)
|
6
|
+
- else
|
7
|
+
= link_to "Enable", admin_page_enable_comments_path(:page_id => page.id), :method => :put
|
data/comments_extension.rb
CHANGED
@@ -1,28 +1,15 @@
|
|
1
|
+
require File.expand_path("../lib/radiant-comments-extension/version", __FILE__)
|
1
2
|
class CommentsExtension < Radiant::Extension
|
2
|
-
version
|
3
|
+
version RadiantCommentsExtension::VERSION
|
3
4
|
description "Adds blog-like comments and comment functionality to pages."
|
4
5
|
url "http://github.com/saturnflyer/radiant-comments"
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
admin.connect 'comments/:status.:format', :controller => 'comments', :status => /all|approved|unapproved/, :conditions => { :method => :get }
|
10
|
-
admin.resources :comments, :member => { :remove => :get, :approve => :put, :unapprove => :put }, :collection => {:destroy_unapproved => :delete}
|
11
|
-
admin.page_enable_comments '/pages/:page_id/comments/enable', :controller => 'comments', :action => 'enable', :conditions => {:method => :put}
|
12
|
-
end
|
13
|
-
map.with_options(:controller => 'admin/comments') do |comments|
|
14
|
-
comments.connect 'admin/pages/:page_id/comments/:status', :status => /all|approved|unapproved/, :conditions => { :method => :get }
|
15
|
-
comments.connect 'admin/pages/:page_id/comments/:status.:format', :status => /all|approved|unapproved/, :conditions => { :method => :get }
|
16
|
-
comments.admin_page_comments 'admin/pages/:page_id/comments/:action'
|
17
|
-
comments.admin_page_comment 'admin/pages/:page_id/comments/:id/:action'
|
18
|
-
end
|
19
|
-
# This needs to be last, otherwise it hoses the admin routes.
|
20
|
-
map.resources :comments, :name_prefix => "page_", :path_prefix => "*url", :controller => "comments"
|
6
|
+
|
7
|
+
extension_config do |config|
|
8
|
+
config.gem 'sanitize'
|
9
|
+
config.gem 'mollom'
|
21
10
|
end
|
22
11
|
|
23
12
|
def activate
|
24
|
-
require 'sanitize'
|
25
|
-
|
26
13
|
Dir["#{File.dirname(__FILE__)}/app/models/*_filter.rb"].each do |file|
|
27
14
|
require file
|
28
15
|
end
|
@@ -37,14 +24,11 @@ class CommentsExtension < Radiant::Extension
|
|
37
24
|
admin.page.index.add :sitemap_head, "index_head_view_comments"
|
38
25
|
admin.page.index.add :node, "index_view_comments"
|
39
26
|
end
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
else
|
46
|
-
admin.tabs.add "Comments", "/admin/comments", :visibility => [:all]
|
47
|
-
end
|
27
|
+
|
28
|
+
tab "Content" do
|
29
|
+
add_item("Comments", "/admin/comments")
|
30
|
+
end.
|
31
|
+
|
48
32
|
require "fastercsv"
|
49
33
|
|
50
34
|
ActiveRecord::Base.class_eval do
|