opinio-bootstrap 0.6.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. data/Gemfile +31 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +197 -0
  4. data/Rakefile +28 -0
  5. data/app/controllers/opinio/comments_controller.rb +47 -0
  6. data/app/views/opinio/comments/_comment.html.erb +20 -0
  7. data/app/views/opinio/comments/_comments.html.erb +12 -0
  8. data/app/views/opinio/comments/_new.html.erb +19 -0
  9. data/app/views/opinio/comments/create.js.erb +16 -0
  10. data/app/views/opinio/comments/destroy.js.erb +3 -0
  11. data/app/views/opinio/comments/index.html.erb +7 -0
  12. data/app/views/opinio/comments/reply.js.erb +6 -0
  13. data/config/locales/opinio.en.yml +16 -0
  14. data/lib/generators/opinio/install/install_generator.rb +38 -0
  15. data/lib/generators/opinio/install/templates/initializers/opinio.erb +28 -0
  16. data/lib/generators/opinio/install/templates/migrations/create_model.rb +13 -0
  17. data/lib/generators/opinio/install/templates/models/model.rb +3 -0
  18. data/lib/generators/opinio/views/views_generator.rb +27 -0
  19. data/lib/opinio.rb +66 -0
  20. data/lib/opinio/controllers/extensions.rb +39 -0
  21. data/lib/opinio/controllers/helpers.rb +22 -0
  22. data/lib/opinio/controllers/internal_helpers.rb +37 -0
  23. data/lib/opinio/controllers/replies.rb +23 -0
  24. data/lib/opinio/opinio_model.rb +77 -0
  25. data/lib/opinio/opinio_model/validations.rb +50 -0
  26. data/lib/opinio/opinio_subjectum.rb +24 -0
  27. data/lib/opinio/orm/active_record.rb +19 -0
  28. data/lib/opinio/rails.rb +8 -0
  29. data/lib/opinio/rails/routes.rb +20 -0
  30. data/lib/opinio/railtie.rb +22 -0
  31. data/lib/opinio/schema.rb +21 -0
  32. data/lib/opinio/shared_examples.rb +89 -0
  33. data/lib/opinio/version.rb +5 -0
  34. metadata +145 -0
data/Gemfile ADDED
@@ -0,0 +1,31 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ gem "kaminari"
6
+ gem "rails", "~> 3.0.0"
7
+ gem "sqlite3"
8
+ gem "jquery-rails"
9
+
10
+ group :development do
11
+ platforms :mri_19 do
12
+ gem "debugger"
13
+ end
14
+ gem 'guard-rspec'
15
+ if RUBY_PLATFORM =~ /darwin/i
16
+ gem 'rb-fsevent'
17
+ gem 'growl'
18
+ end
19
+ end
20
+
21
+ group :test do
22
+ gem "cucumber"
23
+ gem "cucumber-rails"
24
+ gem "capybara"
25
+ gem "launchy"
26
+ gem "database_cleaner"
27
+ gem "rspec-rails", :git => "https://github.com/rspec/rspec-rails.git", :tag => "v2.12.2"
28
+ end
29
+ # To use debugger (ruby-debug for Ruby 1.8.7+, ruby-debug19 for Ruby 1.9.2+)
30
+ # gem 'ruby-debug'
31
+ # gem 'ruby-debug19'
@@ -0,0 +1,20 @@
1
+ Copyright 2011 YOURNAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,197 @@
1
+ # Opinio-Bootstrap #
2
+
3
+ ## Description ##
4
+
5
+ This is a fork of Luiz Pereira's great [Opinio](https://github.com/Draiken/opinio) gem for Rails 3+.
6
+
7
+ It intends to simply utilize [Twitter Bootstrap](http://twitter.github.com/bootstrap/) styling in views out-of-the-box.
8
+
9
+ In particular, it utilizes the media-object component.
10
+
11
+ Currently it requires an image_url(:thumb) method (the default for [Carrierwave](https://github.com/jnicklas/carrierwave)) for your User object; If you are set up with Bootstrap and are storing an image on the User, styling will automatically include the User's avatar along with a link to the user, along with the comment and associated links.
12
+
13
+ So you need a valid call to
14
+
15
+ @user.image_url(:thumb).to_s
16
+
17
+ for the avatar image (media object).
18
+
19
+ To do: make dynamic the method by which User's image is called; provide a default stock placeholder image for those not utilizing an image mounting system for their User object (e.g. Carrierwave/Paperclip).
20
+
21
+ Make sure Bootstrap is in your Rails *Gemfile*:
22
+
23
+ gem "bootstrap-sass", ">= 2.2.2.0"
24
+
25
+ The remainder of this page is [Opinio](https://github.com/Draiken/opinio)'s original README.
26
+
27
+ ## Original ##
28
+
29
+ **IMPORTANT** Version 0.6 might break some behaviour from 0.5 and lower versions, please refer to the [changelog](https://github.com/Draiken/opinio/blob/master/CHANGELOG.rdoc)
30
+
31
+ ## Description ##
32
+
33
+ Opinio is an engine used to add comments behaviour to your application.
34
+ The engine is designed to work only with **Rails 3**
35
+
36
+ ## Intallation ##
37
+
38
+ Simply add the following line to your *Gemfile*:
39
+
40
+ gem "opinio"
41
+
42
+ and run:
43
+
44
+ bundle
45
+
46
+ ## Usage ##
47
+
48
+ Opinio provides generators to facilitate it's usage.
49
+ The most common way to quickly get Opinio working is:
50
+
51
+ rails g opinio:install comment
52
+
53
+ This will generate the `Comment` model, migration and also generate the opinio initializer for customization of the engine.
54
+ A `opinio_model` will be added on the `routes.rb`. This method adds the default urls for the model that will act as the comment
55
+ in your app.
56
+
57
+ In order to add the comments functionality to a model, you use the `opinio_subjectum` method
58
+
59
+ class Post < ActiveRecord::Base
60
+ opinio_subjectum
61
+ end
62
+
63
+ On the `routes.rb` you should simply add an `opinio` for your commentable resource
64
+
65
+ resources :posts do
66
+ opinio
67
+ end
68
+
69
+ To render the comments in your views, there is a helper to display the resource's comments easily:
70
+
71
+ <%= comments_for @post %>
72
+
73
+ This will render the comments and a form for new comment submissions. Alternatively you can render just the comments or just the form like this:
74
+
75
+ <%= render_comments @post %>
76
+ <%= render_comments_form @post %>
77
+
78
+ If you need to render the comments with a specific page or limit
79
+ you can use Kaminari's configurations like `paginates_per 10` on your comment model
80
+ or you can customize it for specific views on the helpers
81
+
82
+ <%= render_comments @post, :page => params[:page], :limit => 5 %>
83
+
84
+ This options can also be used on the `comments_for` helper.
85
+
86
+ ## Customization ##
87
+
88
+ ### Views ###
89
+
90
+ Of course you will want to customize how the comments are displayed or any other customization to the view. To generate the view files on your application, run:
91
+
92
+ rails g opinio:views
93
+
94
+ And you can customize all the views used by the engine.
95
+
96
+ ### Behaviour ###
97
+
98
+ #### Opinio Model ####
99
+
100
+ You can customize the opinio model to suit your needs. The basic customization is the owner class name. It defaults to `User` but you can change that in the initializer or in the model by passing the `:owner_class_name => "MyOwnerClass"` option to the `opinio_model` method call.
101
+
102
+ Another customization you can do is set the `counter_cache` to true in the commentable model. You can use the `:counter_cache` option for that.
103
+
104
+ The other two customizations are only made through the initializer, and they are the `accept_replies` which defaults to true and `strip_html_tags_on_save` which also defaults to `true`.
105
+
106
+ Validations on the opinio model are very basic, just ensuring it has a body, a commentable and an owner, if you want any other kind of validation, like the minimum size of a comment, you can use the regular AR validations as you wish.
107
+
108
+ Remember that if you use titles, you need to add that to your comments table, since the generator doesn't add it by default.
109
+
110
+ #### Opinio Subjectum ####
111
+
112
+ To change how the models that actually have the comments, you can customize them with any option you would use to a regular `has_many` relationship in ActiveRecord.
113
+
114
+ The default options are these:
115
+
116
+ has_many :comments,
117
+ :class_name => Opinio.model_name,
118
+ :as => :commentable,
119
+ :order => "created_at #{Opinio.sort_order}"),
120
+ :dependent => :destroy
121
+
122
+ The `sort_order` and `model_name` are both setup in the initializer. Here you can do things like, let's say you have moderation in your comments, you can only show the approved comments:
123
+
124
+ opinio_subjectum :conditions => ["approved = ?", true]
125
+
126
+ Remember you can override any of these options (except `as`) by passing them to the `opinio_subjectum` method.
127
+
128
+ #### Pretty Urls ####
129
+
130
+ Often times you will want the engine to show the index of comments for a specific item
131
+ without having to pass the `:commentable_type` or `:commentable_id` parameters.
132
+
133
+ In order to do that, opinio provides a method to `ActionController::Base`:
134
+
135
+ opinio_identifier do |params|
136
+ next Review.find(params[:review_id]) if params[:review_id]
137
+ next Product.find(params[:product_id]) if params[:product_id]
138
+ end
139
+
140
+ Note: you use next instead of return because it is a proc that will be executed later on, and you cannot return on procs
141
+
142
+ Basically on this method you receive the `params` variable and you tell the engine, who owns
143
+ the comments from that page.
144
+ This allows you to use routes like:
145
+
146
+ /products/1/comments
147
+ /products/1/reviews/1/comments
148
+
149
+ Without passing those 2 parameters.
150
+ I suggest you put this method on the `ApplicationController`
151
+
152
+ #### Customize destroy conditions ####
153
+
154
+ By default, noone can destroy a comment in the engine. You have to tell the engine who can do it.
155
+ To setup a custom destroy condition use the methods provided by opinio
156
+ in our controllers. For instance, if our opinio model is called 'comment'
157
+ it could be written like this:
158
+
159
+ comment_destroy_conditions do |comment|
160
+ comment.owner == current_user
161
+ end
162
+
163
+ This would make users only be able to remove their own comments.
164
+ Another example would be using the `CanCan`:
165
+
166
+ comment_destroy_conditions do |comment|
167
+ authorize :destroy, comment
168
+ end
169
+
170
+ You get the picture, you're inside your controller's methods on that block
171
+ so you can call anything your normal controllers call on actions.
172
+
173
+ ### Testing ###
174
+
175
+ Opinio provides a few shared examples for testing of your model with rspec
176
+ On your opinio model test case you can require opinio's shared examples and use them
177
+
178
+ require 'opinio/shared_examples'
179
+
180
+ describe Comment do
181
+ it_should_behave_like :opinio
182
+ end
183
+
184
+ describe Post do
185
+ it_should_behave_like :opinio_subjectum
186
+ end
187
+
188
+ ## Contribution ##
189
+
190
+ If you want to help in any way with **Opinio** please message me or fork the project, make the changes and send me a pull request.
191
+ For issues please use the github [issues tracker](https://github.com/Draiken/opinio/issues)
192
+
193
+ ### TODO ###
194
+
195
+ * Refactor the `comments_for` helper
196
+ * Extract documentation to wiki
197
+ * Add mongoid support
@@ -0,0 +1,28 @@
1
+ # encoding: UTF-8
2
+ require 'rubygems'
3
+ begin
4
+ require 'bundler/setup'
5
+ rescue LoadError
6
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
7
+ end
8
+
9
+ require 'rake'
10
+ require 'rdoc/task'
11
+
12
+ require 'cucumber'
13
+ require 'cucumber/rake/task'
14
+
15
+ require 'rspec/core'
16
+ require 'rspec/core/rake_task'
17
+
18
+ RSpec::Core::RakeTask.new(:spec)
19
+
20
+ task :default => :spec
21
+
22
+ Rake::RDocTask.new(:rdoc) do |rdoc|
23
+ rdoc.rdoc_dir = 'rdoc'
24
+ rdoc.title = 'Opinio'
25
+ rdoc.options << '--line-numbers' << '--inline-source'
26
+ rdoc.rdoc_files.include('README.rdoc')
27
+ rdoc.rdoc_files.include('lib/**/*.rb')
28
+ end
@@ -0,0 +1,47 @@
1
+ class Opinio::CommentsController < ApplicationController
2
+ include Opinio::Controllers::InternalHelpers
3
+ include Opinio::Controllers::Replies if Opinio.accept_replies
4
+
5
+ def index
6
+ @comments = resource.comments.page(params[:page])
7
+ end
8
+
9
+ def create
10
+ @comment = resource.comments.build(params[:comment])
11
+ @comment.owner = send(Opinio.current_user_method)
12
+ if @comment.save
13
+ flash_area = :notice
14
+ message = t('opinio.messages.comment_sent')
15
+ else
16
+ flash_area = :error
17
+ message = t('opinio.messages.comment_sending_error')
18
+ end
19
+
20
+ respond_to do |format|
21
+ format.js
22
+ format.html do
23
+ set_flash(flash_area, message)
24
+ redirect_to(opinio_after_create_path(resource))
25
+ end
26
+ end
27
+ end
28
+
29
+ def destroy
30
+ @comment = Opinio.model_name.constantize.find(params[:id])
31
+
32
+ if can_destroy_opinio?(@comment)
33
+ @comment.destroy
34
+ set_flash(:notice, t('opinio.messages.comment_destroyed'))
35
+ else
36
+ #flash[:error] = I18n.translate('opinio.comment.not_permitted', :default => "Not permitted")
37
+ logger.warn "user #{send(Opinio.current_user_method)} tried to remove a comment from another user #{@comment.owner.id}"
38
+ render :text => "unauthorized", :status => 401 and return
39
+ end
40
+
41
+ respond_to do |format|
42
+ format.js
43
+ format.html { redirect_to( opinio_after_destroy_path(@comment) ) }
44
+ end
45
+ end
46
+
47
+ end
@@ -0,0 +1,20 @@
1
+ <% reply = defined?(reply) ? reply : false %>
2
+ <div id="comment_<%= comment.id %>" class="media">
3
+ <a class="pull-left" href="<%= user_path(comment.owner) %>">
4
+ <img class="media-object" src="<%= comment.owner.image_url(:thumb).to_s %>" />
5
+ <span><%= comment.owner.name.titlecase %></span>
6
+ </a>
7
+ <div class="media-body">
8
+ <%= simple_format(comment.body) %>
9
+ <% if comment.owner == send(Opinio.current_user_method) %>
10
+ <%= link_to t('opinio.actions.delete'), comment_path(comment), :method => :delete, :remote => true %>
11
+ <% end %>
12
+ <%# this enables only 1 level of replies %>
13
+ <% if Opinio.accept_replies && !reply && !(comment.owner == current_user) %>
14
+ <span><%= link_to t('opinio.actions.reply'), reply_comment_path(comment), :remote => true %></span>
15
+ <% end %>
16
+ <div id="comment_<%= comment.id %>_replies" class="replies media">
17
+ <%= render :partial => "opinio/comments/comment", :collection => comment.comments, :locals => {:reply => true} %>
18
+ </div>
19
+ </div>
20
+ </div>
@@ -0,0 +1,12 @@
1
+ <div>
2
+ <%= paginate comments %>
3
+ </div>
4
+ <% unless comments.empty? %>
5
+ <%= render :partial => 'opinio/comments/comment', :collection => comments %>
6
+ <% else %>
7
+ <h3><%= t('opinio.messages.no_comments_found') %></h3>
8
+ <% end %>
9
+
10
+ <div>
11
+ <%= paginate comments %>
12
+ </div>
@@ -0,0 +1,19 @@
1
+ <div id="new_comment">
2
+ <% if send(Opinio.current_user_method) %>
3
+ <h3 class="addcomment"><%= t('opinio.messages.add_comment') %></h3>
4
+ <%= form_for Comment.new, :remote => false do |f| %>
5
+ <p>
6
+ <%= f.text_area :body, :placeholder => "Enter comment...", rows:"10" %>
7
+ </p>
8
+ <%= hidden_field_tag :commentable_id, commentable.id %>
9
+ <%= hidden_field_tag :commentable_type, commentable.class.base_class.name.to_s %>
10
+ <%= hidden_field_tag :original_controller, params[:controller] %>
11
+ <%= hidden_field_tag :original_id, params[:id] %>
12
+ <%= f.submit t('opinio.actions.add') %>
13
+ <% end %>
14
+ <% else %>
15
+ <p>
16
+ <%= t('opinio.messages.must_be_logged_in_to_comment') %>
17
+ </p>
18
+ <% end %>
19
+ </div>
@@ -0,0 +1,16 @@
1
+ $('#no_comments').hide();
2
+ <% if @comment.valid? %>
3
+ <% if @reply %>
4
+ if($('#comment_<%= @comment.commentable_id %> ul').length == 0)
5
+ $('#comment_<%= @comment.commentable_id %>').append('<ul id="comment_<%= @comment.commentable_id %>_replies" class="replies"></ul>');
6
+ $('#comment_<%= @comment.commentable_id %>_replies').append("<%= escape_javascript( render @comment, :locals => {:reply => @reply} ) %>");
7
+ $('#commentable_id').val('<%= @comment.commentable.commentable_id %>');
8
+ $('#commentable_type').val('<%= @comment.commentable.commentable_type %>');
9
+ <% else %>
10
+ $('#comments').<%= Opinio.sort_order == 'ASC' ? 'append' : 'prepend'%>("<%= escape_javascript( render @comment, :locals => {:reply => @reply} ) %>");
11
+ <% end %>
12
+ $('textarea#comment_body').val('');
13
+ <% else %>
14
+ $('#comments').prepend("<%= escape_javascript(flash[:notice]) %>");
15
+ $('#comments').prepend("<%= escape_javascript(flash[:error]) %>");
16
+ <% end %>
@@ -0,0 +1,3 @@
1
+ jQuery('#comment_<%= @comment.id %>').remove();
2
+ jQuery('#comments').prepend("<%= escape_javascript(flash[:notice]) %>");
3
+ jQuery('#comments').prepend("<%= escape_javascript(flash[:error]) %>");
@@ -0,0 +1,7 @@
1
+ <h1><%= t('opinio.messages.comments') %></h1>
2
+
3
+ <%= paginate @comments %>
4
+
5
+ <%= render :partial => "opinio/comments/comments", :locals => { :comments => @comments } %>
6
+
7
+ <%= paginate @comments %>
@@ -0,0 +1,6 @@
1
+ <% @comment = Comment.find(@commentable_id) %>
2
+ $("#commentable_id").val('<%= @commentable_id %>');
3
+ $("#commentable_type").val('<%= @commentable_type %>');
4
+ $("#new_comment textarea").val('');
5
+ $("#new_comment h3.addcomment").text('Replying to <%= @comment.owner.name.titlecase %>');
6
+ $("#new_comment textarea").focus();
@@ -0,0 +1,16 @@
1
+ en:
2
+ opinio:
3
+ actions:
4
+ delete: 'Delete'
5
+ reply: 'Reply'
6
+ add: 'Add comment'
7
+ messages:
8
+ no_comments_found: 'No comments found'
9
+ must_be_logged_in_to_comment: 'Must be logged in to comment.'
10
+ add_comment: 'Add comment'
11
+ comments: 'Comments'
12
+ comment_sent: 'Comment sent successfully.'
13
+ comment_sending_error: 'Error while sending the comment.'
14
+ comment_destroyed: 'Comment removed successfully.'
15
+ comment_interval: 'You must wait %{time} seconds to comment again.'
16
+ cannot_be_comment_of_comment: "Cannot reply another comment's reply"
@@ -0,0 +1,38 @@
1
+ module Opinio
2
+ module Generators
3
+ class InstallGenerator < Rails::Generators::NamedBase
4
+ include Rails::Generators::Migration
5
+
6
+ source_root File.expand_path('../templates', __FILE__)
7
+
8
+
9
+ def generate_model
10
+ template "models/model.rb", "app/models/#{file_name}.rb"
11
+ end
12
+
13
+ def generate_migration
14
+ migration_template "migrations/create_model.rb", "db/migrate/create_#{table_name}", {:assigns => {:name => name, :table_name => table_name}}
15
+ end
16
+
17
+ def generate_initializer
18
+ template "initializers/opinio.erb", "config/initializers/opinio.rb"
19
+ end
20
+
21
+ def generate_route
22
+ route "opinio_model"
23
+ end
24
+
25
+ def add_dependency_gem
26
+ gem "kaminari"
27
+ end
28
+
29
+ def self.next_migration_number(dirname)
30
+ if ActiveRecord::Base.timestamped_migrations
31
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
32
+ else
33
+ "%.3d" % (current_migration_number(dirname) + 1)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,28 @@
1
+ # Opinio Configuration
2
+
3
+ Opinio.setup do |config|
4
+
5
+ # Use this to change the class that calls +opinio+ method
6
+ config.model_name = "<%= class_name %>"
7
+
8
+ # This is the owner class of the comment (opinio model)
9
+ # config.owner_class_name = "User"
10
+
11
+ # Change this if you do not want to allow replies on your comments
12
+ # config.accept_replies = true
13
+
14
+ # Here you can change the method called to check who is the current user
15
+ # config.current_user_method = :current_user
16
+
17
+ # Strip html tags on save comment
18
+ config.strip_html_tags_on_save = true
19
+
20
+ # Comments sort order by created_at (DESC or ASC)
21
+ # Note: you can override that easily within each opinio subjectum
22
+ config.sort_order = 'DESC'
23
+
24
+ # Wether or not the default opinio controller should set the flash
25
+ # when creating/removing comments
26
+ config.set_flash = true
27
+
28
+ end
@@ -0,0 +1,13 @@
1
+ class Create<%= table_name.camelize %> < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :<%= table_name %> do |t|
4
+ t.opinio
5
+
6
+ t.timestamps
7
+ end
8
+ end
9
+
10
+ def self.down
11
+ drop_table :<%= table_name %>
12
+ end
13
+ end
@@ -0,0 +1,3 @@
1
+ class <%= class_name %> < ActiveRecord::Base
2
+ opinio
3
+ end
@@ -0,0 +1,27 @@
1
+ module Opinio
2
+ module Generators
3
+ class ViewsGenerator < Rails::Generators::Base
4
+
5
+ source_root File.expand_path('../../../../../app/views/opinio/comments', __FILE__)
6
+
7
+ class_option :haml, :desc => 'Generate HAML views instead of ERB.', :type => :boolean
8
+
9
+ def copy_views
10
+ html_names = ["_comment", "_comments", "_new", "index"]
11
+ js_names = ["create", "destroy", "reply"]
12
+ html_names.each do |name|
13
+ copy_file "#{name}.html.#{view_language}", "app/views/opinio/comments/#{name}.html.#{view_language}"
14
+ end
15
+ js_names.each do |name|
16
+ copy_file "#{name}.js.erb", "app/views/opinio/comments/#{name}.js.erb"
17
+ end
18
+ end
19
+
20
+ def view_language
21
+ options.haml? ? 'haml' : 'erb'
22
+ end
23
+
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,66 @@
1
+ module Opinio
2
+ require 'opinio/schema'
3
+
4
+ module Controllers
5
+ require 'opinio/controllers/helpers'
6
+ require 'opinio/controllers/internal_helpers'
7
+ require 'opinio/controllers/replies'
8
+ end
9
+
10
+ require 'opinio/railtie'
11
+ require 'opinio/rails'
12
+ require 'opinio/orm/active_record'
13
+
14
+ mattr_accessor :model_name
15
+ @@model_name = "Comment"
16
+
17
+ mattr_accessor :owner_class_name
18
+ @@owner_class_name = "User"
19
+
20
+ mattr_accessor :use_title
21
+ @@use_title = false
22
+
23
+ mattr_accessor :accept_replies
24
+ @@accept_replies = false
25
+
26
+ mattr_accessor :custom_identifiers
27
+ @@custom_identifiers = Array.new
28
+
29
+ mattr_accessor :interval_between_comments
30
+ @@interval_between_comments = false
31
+
32
+ mattr_accessor :destroy_conditions
33
+ @@destroy_conditions = Proc.new { false }
34
+
35
+ mattr_accessor :current_user_method
36
+ @@current_user_method = :current_user
37
+
38
+ mattr_accessor :strip_html_tags_on_save
39
+ @@strip_html_tags_on_save = true
40
+
41
+ mattr_accessor :sort_order
42
+ @@sort_order = 'DESC'
43
+
44
+ mattr_accessor :set_flash
45
+ @@set_flash = true
46
+
47
+ def self.setup
48
+ yield self
49
+ end
50
+
51
+ def self.opinio_identifier(block)
52
+ @@custom_identifiers << block
53
+ end
54
+
55
+ def self.set_destroy_conditions(&block)
56
+ @@destroy_conditions = block
57
+ end
58
+
59
+ def self.check_custom_identifiers(params)
60
+ self.custom_identifiers.each do |identifier|
61
+ identified = identifier.call(params)
62
+ return identified unless identified.nil?
63
+ end
64
+ nil
65
+ end
66
+ end
@@ -0,0 +1,39 @@
1
+ module Opinio
2
+ module Controllers
3
+ module Extensions
4
+
5
+ def self.included(base)
6
+ base.extend ClassMethods
7
+ base.send :include, InstanceMethods
8
+
9
+ base.class_eval do
10
+ (class << self; self; end).instance_eval do
11
+ define_method "#{Opinio.model_name.underscore}_destroy_conditions" do |&block|
12
+ Opinio.set_destroy_conditions( &block )
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ module ClassMethods
19
+ def opinio_identifier(&block)
20
+ Opinio.opinio_identifier(block)
21
+ end
22
+ end
23
+
24
+ module InstanceMethods
25
+ def can_destroy_opinio?(opinio)
26
+ self.instance_exec(opinio, &Opinio.destroy_conditions)
27
+ end
28
+
29
+ def opinio_after_create_path(resource)
30
+ resource.is_a?(Opinio.model_name.constantize) ? resource.commentable : resource
31
+ end
32
+
33
+ def opinio_after_destroy_path(comment)
34
+ comment.commentable
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,22 @@
1
+ module Opinio
2
+ module Controllers
3
+ module Helpers
4
+
5
+ def comments_for(object, options = {})
6
+ render_comments(object, options) +
7
+ ( render_comments_form(object, options) unless options[:no_new] ).to_s
8
+ end
9
+
10
+ def render_comments(object, options = {})
11
+ limit = options.delete(:limit) || Opinio.model_name.constantize.default_per_page
12
+ page = options.delete(:page) || 1
13
+ render( :partial => "opinio/comments/comments", :locals => {:comments => object.comments.page(page).limit(limit), :commentable => object, :options => options} )
14
+ end
15
+
16
+ def render_comments_form(object, options = {})
17
+ render( :partial => "opinio/comments/new", :locals => {:commentable => object, :options => options} )
18
+ end
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,37 @@
1
+ module Opinio
2
+ module Controllers
3
+ module InternalHelpers
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ helper_method :resource, :resource_name
8
+ end
9
+
10
+ def resource
11
+ @resource ||= custom_resource_identifier(params) || resource_by_params
12
+ end
13
+
14
+ def resource_by_params
15
+ if params[:commentable_type]
16
+ params[:commentable_type].constantize.find(params[:commentable_id])
17
+ elsif params[:comment]
18
+ params[:comment][:commentable_type].constantize.find(params[:comment][:commentable_id])
19
+ else
20
+ raise "Unable to determine comments holder"
21
+ end
22
+ end
23
+
24
+ def custom_resource_identifier(params)
25
+ Opinio.check_custom_identifiers(params)
26
+ end
27
+
28
+ def resource_name
29
+ Opinio.model_name
30
+ end
31
+
32
+ def set_flash(name, message)
33
+ flash[name] = message if Opinio.set_flash
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,23 @@
1
+ module Opinio
2
+ module Controllers
3
+ module Replies
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ before_filter :check_reply, :only => [:create]
8
+ end
9
+
10
+ def reply
11
+ @commentable_type = Opinio.model_name
12
+ @commentable_id = params[:id]
13
+ @commentable = Opinio.model_name.constantize.find(params[:id])
14
+ end
15
+
16
+ private
17
+
18
+ def check_reply
19
+ @reply = params[:commentable_type] == Opinio.model_name
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,77 @@
1
+ require 'opinio/opinio_model/validations'
2
+
3
+ module Opinio
4
+ module OpinioModel
5
+
6
+ def self.included(base)
7
+ base.extend ClassMethods
8
+ end
9
+
10
+ module ClassMethods
11
+
12
+ # Adds the Opinio functionallity to the model
13
+ # You can pass a hash of options to customize the Opinio model
14
+ def opinio(*args)
15
+ return if self.included_modules.include?(Opinio::OpinioModel::Validations)
16
+ options = args.extract_options!
17
+
18
+ if Opinio.use_title
19
+ attr_accessible :title
20
+ end
21
+ attr_accessible :body
22
+
23
+ belongs_to :commentable, :polymorphic => true, :counter_cache => options.fetch(:counter_cache, false)
24
+ belongs_to :owner, :class_name => options.fetch(:owner_class_name, Opinio.owner_class_name)
25
+
26
+ scope :owned_by, lambda {|owner| where('owner_id = ?', owner.id) }
27
+
28
+ send :include, Opinio::OpinioModel::Validations
29
+
30
+ if Opinio.accept_replies
31
+ send :include, RepliesSupport
32
+ end
33
+
34
+ if Opinio.strip_html_tags_on_save
35
+ send :include, Sanitizing
36
+ end
37
+
38
+ end
39
+ end
40
+
41
+ module Sanitizing
42
+ def self.included(base)
43
+ base.class_eval do
44
+ send :include, ActionView::Helpers::SanitizeHelper
45
+ before_save :strip_html_tags
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def strip_html_tags
52
+ self.body = strip_tags(self.body)
53
+ end
54
+ end
55
+
56
+ module RepliesSupport
57
+ def self.included(base)
58
+ base.class_eval do
59
+ validate :cannot_be_comment_of_a_comments_comment
60
+ opinio_subjectum :order => 'created_at ASC'
61
+ end
62
+ end
63
+
64
+ private
65
+
66
+ # Validates that you cannot comment on a comment's comment
67
+ def cannot_be_comment_of_a_comments_comment
68
+ if new_record? && self.commentable_type == Opinio.model_name
69
+ if commentable.commentable_type == Opinio.model_name
70
+ errors.add :base, I18n.t('opinio.messages.cannot_be_comment_of_comment')
71
+ end
72
+ end
73
+ end
74
+
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,50 @@
1
+ module Opinio
2
+ module OpinioModel
3
+ module Validations
4
+
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+
9
+ #TODO: refactor this
10
+ if Opinio.interval_between_comments
11
+ include Opinio::OpinioModel::Validations::IntervalMethods
12
+ validate :validate_last_comment_time, :if => :new_record?
13
+ cattr_accessor :comments_interval
14
+ self.comments_interval = options.reverse_merge(:time => Opinio.interval_between_comments)[:time]
15
+ end
16
+
17
+ validates :body, :presence => true
18
+ validates :commentable, :presence => true
19
+ validates :owner, :presence => true, :associated => true
20
+
21
+ end
22
+
23
+
24
+ module IntervalMethods
25
+
26
+ private
27
+
28
+ # Checks the time of the last comment
29
+ # made by the same owner
30
+ def validate_last_comment_time
31
+ last_comment = Comment.owned_by(self.owner).order('created_at DESC').last
32
+ if last_comment
33
+ if (Time.now - last_comment.created_at).round >= self.comments_interval
34
+ true
35
+ else
36
+ errors.add(:created_at,
37
+ I18n.translate('opinio.messages.comment_interval',
38
+ :time => self.comments_interval))
39
+ false
40
+ end
41
+ else
42
+ true
43
+ end
44
+ end
45
+
46
+ end # IntervalMethods
47
+
48
+ end # Validations
49
+ end # OpinioModel
50
+ end # Opinio
@@ -0,0 +1,24 @@
1
+ module Opinio
2
+ module OpinioSubjectum
3
+
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ module ClassMethods
9
+ def opinio_subjectum(*args)
10
+ options = args.extract_options!
11
+ options.delete(:as)
12
+
13
+ default_options = { :class_name => Opinio.model_name,
14
+ :as => :commentable,
15
+ :order => "created_at #{Opinio.sort_order}",
16
+ :dependent => :destroy }
17
+
18
+ has_many :comments, default_options.merge(options)
19
+
20
+ end
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,19 @@
1
+ require 'active_record/connection_adapters/abstract/schema_definitions'
2
+
3
+ module Opinio
4
+ module Orm
5
+ module ActiveRecord
6
+ module Schema
7
+ include Opinio::Schema
8
+ # Tell how to apply schema methods.
9
+ def apply_opinio_schema(name, type, options={})
10
+ column name, type.to_s.downcase.to_sym, options
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ ActiveRecord::ConnectionAdapters::Table.send :include, Opinio::Orm::ActiveRecord::Schema
18
+ ActiveRecord::ConnectionAdapters::TableDefinition.send :include, Opinio::Orm::ActiveRecord::Schema
19
+
@@ -0,0 +1,8 @@
1
+ require File.join(File.dirname(__FILE__), 'rails', 'routes')
2
+
3
+ module Opinio
4
+ class Engine < ::Rails::Engine
5
+
6
+ end
7
+ end
8
+
@@ -0,0 +1,20 @@
1
+ module ActionDispatch::Routing
2
+ class Mapper
3
+ def opinio(*args)
4
+ options = args.extract_options!.symbolize_keys
5
+ route_name = options[:path_name] || Opinio.model_name.pluralize.downcase
6
+ options[:controller] ||= 'opinio/comments'
7
+
8
+ get "#{ route_name }(/:page)" => "#{options[:controller].to_s}#index", :as => :comments
9
+ end
10
+
11
+ def opinio_model(*args)
12
+ options = args.extract_options!
13
+ options[:controller] ||= 'opinio/comments'
14
+ resources :comments, options do
15
+ get 'reply', :on => :member if Opinio.accept_replies
16
+ end
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,22 @@
1
+ require 'rails'
2
+
3
+ module Opinio
4
+ class Railtie < ::Rails::Railtie #:nodoc:
5
+ initializer 'opinio' do |app|
6
+ ActiveSupport.on_load(:active_record) do
7
+ require File.join(File.dirname(__FILE__), 'opinio_model')
8
+ require File.join(File.dirname(__FILE__), 'opinio_subjectum')
9
+ ::ActiveRecord::Base.send :include, Opinio::OpinioModel
10
+ ::ActiveRecord::Base.send :include, Opinio::OpinioSubjectum
11
+ end
12
+ ActiveSupport.on_load(:action_view) do
13
+ require File.join(File.dirname(__FILE__), 'controllers', 'helpers')
14
+ ::ActionView::Base.send :include, Opinio::Controllers::Helpers
15
+ end
16
+ ActiveSupport.on_load(:action_controller) do
17
+ require File.join(File.dirname(__FILE__), 'controllers', 'extensions')
18
+ ::ActionController::Base.send :include, Opinio::Controllers::Extensions
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,21 @@
1
+ module Opinio
2
+ module Schema
3
+
4
+ def opinio(options = {})
5
+ null = options[:null] || false
6
+ default = options.key?(:default) ? options[:default] : ("" if null == false)
7
+
8
+ apply_opinio_schema :owner_id, :integer, :null => false
9
+ apply_opinio_schema :commentable_id, :integer, :null => false
10
+ apply_opinio_schema :commentable_type, :string, :null => false
11
+ apply_opinio_schema :title, :string, :default => default, :null => null if options[:title]
12
+ apply_opinio_schema :body, :text, :null => false
13
+ end
14
+
15
+ # Overwrite with specific modification to create your own schema.
16
+ def apply_opinio_schema(name, type, options={})
17
+ raise NotImplementedError
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,89 @@
1
+ shared_examples_for :opinio do
2
+
3
+ let(:comment) { Comment.new }
4
+ let(:owner) { User.create }
5
+
6
+ before(:all) do
7
+ @post = Post.new(:title => "My first post", :body => "Damn I really suck at writing")
8
+ @post.save
9
+ end
10
+
11
+ it "should have the correct attributes" do
12
+ should respond_to(:owner)
13
+ should respond_to(:body)
14
+ should respond_to(:commentable)
15
+ end
16
+
17
+
18
+ it "should not allow comments of comments" do
19
+ Comment.class_eval do
20
+ include Opinio::OpinioModel::RepliesSupport
21
+ end
22
+
23
+ c = Comment.new(:body => "The Comment !")
24
+ c.commentable = @post
25
+ c.owner = owner
26
+ c.save.should == true
27
+
28
+ c2 = Comment.new(:body => "The Comment !")
29
+ c2.owner = owner
30
+ c2.commentable = c
31
+ c2.save.should == true
32
+
33
+ c3 = c2.dup
34
+ c3.commentable = c2
35
+ c3.save.should == false
36
+
37
+ c3.errors[:base].count.should == 1
38
+ end
39
+
40
+ it "should validate presence of body and commentable" do
41
+ comment.should be_invalid
42
+ comment.errors[:body].should be_present
43
+ comment.errors[:commentable].should be_present
44
+ end
45
+
46
+ it "and it should insist on having an owner" do
47
+ c = Comment.new(:body => "The Comment !")
48
+ c.commentable = @post
49
+ c.save.should == false
50
+ end
51
+
52
+ context "when strip_html_tags_on_save is true" do
53
+ it "should strip html tags" do
54
+ comment = create_valid_comment('<h1>Chuck will save us!</h1>')
55
+ comment.body.should == 'Chuck will save us!'
56
+ end
57
+ end
58
+
59
+ context "when strip_html_tags_on_save is false" do
60
+
61
+ # we have to create a different class
62
+ # because opinio adds the strip tags callbacks
63
+ # only once when opinio is called on the class
64
+ class NoSanitizerComment < ActiveRecord::Base
65
+ self.table_name = :comments
66
+ end
67
+
68
+ before do
69
+ Opinio.stub(:strip_html_tags_on_save).and_return(false)
70
+ NoSanitizerComment.class_eval do
71
+ opinio
72
+ end
73
+ end
74
+ it "should not strip html tags" do
75
+ comment = create_valid_comment('<h1>Chuck will save us!</h1>', NoSanitizerComment)
76
+ comment.body.should == '<h1>Chuck will save us!</h1>'
77
+ end
78
+ end
79
+
80
+ end
81
+
82
+
83
+ shared_examples_for :opinio_subjectum do
84
+
85
+ it "and should add opinio_subjectum functionallity" do
86
+ should respond_to(:comments)
87
+ end
88
+
89
+ end
@@ -0,0 +1,5 @@
1
+ module Opinio
2
+ class Version
3
+ VERSION = '0.6.0.2'
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,145 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: opinio-bootstrap
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.6.0.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Luiz Felipe Garcia Pereira
9
+ - Richard Carey
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2013-02-10 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rails
17
+ requirement: !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ~>
21
+ - !ruby/object:Gem::Version
22
+ version: '3'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ~>
29
+ - !ruby/object:Gem::Version
30
+ version: '3'
31
+ - !ruby/object:Gem::Dependency
32
+ name: kaminari
33
+ requirement: !ruby/object:Gem::Requirement
34
+ none: false
35
+ requirements:
36
+ - - ! '>='
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ type: :runtime
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: jquery-rails
49
+ requirement: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ - !ruby/object:Gem::Dependency
64
+ name: bootstrap-sass
65
+ requirement: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ! '>='
69
+ - !ruby/object:Gem::Version
70
+ version: 2.2.2.0
71
+ type: :runtime
72
+ prerelease: false
73
+ version_requirements: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: 2.2.2.0
79
+ description: Opinio is an engine used to add comments functionallity to rails 3 applications;
80
+ this version is adapted to Twitter Bootstrap.
81
+ email:
82
+ - luiz.felipe.gp@gmail.com
83
+ - rc@rcdmcg.com
84
+ executables: []
85
+ extensions: []
86
+ extra_rdoc_files: []
87
+ files:
88
+ - app/controllers/opinio/comments_controller.rb
89
+ - app/views/opinio/comments/_comment.html.erb
90
+ - app/views/opinio/comments/_comments.html.erb
91
+ - app/views/opinio/comments/_new.html.erb
92
+ - app/views/opinio/comments/create.js.erb
93
+ - app/views/opinio/comments/destroy.js.erb
94
+ - app/views/opinio/comments/index.html.erb
95
+ - app/views/opinio/comments/reply.js.erb
96
+ - lib/generators/opinio/install/install_generator.rb
97
+ - lib/generators/opinio/install/templates/initializers/opinio.erb
98
+ - lib/generators/opinio/install/templates/migrations/create_model.rb
99
+ - lib/generators/opinio/install/templates/models/model.rb
100
+ - lib/generators/opinio/views/views_generator.rb
101
+ - lib/opinio/controllers/extensions.rb
102
+ - lib/opinio/controllers/helpers.rb
103
+ - lib/opinio/controllers/internal_helpers.rb
104
+ - lib/opinio/controllers/replies.rb
105
+ - lib/opinio/opinio_model/validations.rb
106
+ - lib/opinio/opinio_model.rb
107
+ - lib/opinio/opinio_subjectum.rb
108
+ - lib/opinio/orm/active_record.rb
109
+ - lib/opinio/rails/routes.rb
110
+ - lib/opinio/rails.rb
111
+ - lib/opinio/railtie.rb
112
+ - lib/opinio/schema.rb
113
+ - lib/opinio/shared_examples.rb
114
+ - lib/opinio/version.rb
115
+ - lib/opinio.rb
116
+ - config/locales/opinio.en.yml
117
+ - MIT-LICENSE
118
+ - Rakefile
119
+ - Gemfile
120
+ - README.md
121
+ homepage: https://github.com/rceee/opinio-bootstrap
122
+ licenses: []
123
+ post_install_message:
124
+ rdoc_options: []
125
+ require_paths:
126
+ - lib
127
+ required_ruby_version: !ruby/object:Gem::Requirement
128
+ none: false
129
+ requirements:
130
+ - - ! '>='
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ required_rubygems_version: !ruby/object:Gem::Requirement
134
+ none: false
135
+ requirements:
136
+ - - ! '>='
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ requirements: []
140
+ rubyforge_project:
141
+ rubygems_version: 1.8.24
142
+ signing_key:
143
+ specification_version: 3
144
+ summary: A rails 3 engine for comments, with Twitter Bootstrap styling.
145
+ test_files: []