seems_rateable 1.0.13 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +20 -0
  3. data/Gemfile +3 -0
  4. data/README.md +41 -61
  5. data/Rakefile +8 -2
  6. data/app/assets/javascripts/seems_rateable/index.js +1 -0
  7. data/{lib/generators/seems_rateable/install/templates → app/assets/javascripts/seems_rateable}/jRating.js.erb +225 -225
  8. data/app/assets/stylesheets/seems_rateable/{application.css → index.css.erb} +2 -2
  9. data/app/controllers/seems_rateable/application_controller.rb +1 -3
  10. data/app/controllers/seems_rateable/rates_controller.rb +31 -0
  11. data/app/models/seems_rateable/rate.rb +2 -2
  12. data/bin/rails +8 -0
  13. data/config/routes.rb +1 -1
  14. data/lib/generators/seems_rateable/install/install_generator.rb +34 -28
  15. data/lib/generators/seems_rateable/install/templates/initializer.rb +5 -2
  16. data/lib/generators/seems_rateable/install/templates/rateable.js.erb +12 -22
  17. data/lib/generators/seems_rateable/install/templates/rates_migration.rb +6 -9
  18. data/lib/generators/seems_rateable/manifest_finder.rb +25 -0
  19. data/lib/generators/seems_rateable/migration_helpers.rb +21 -0
  20. data/lib/generators/seems_rateable/uninstall/templates/drop_seems_rateable_rates_table.rb +9 -0
  21. data/lib/generators/seems_rateable/uninstall/uninstall_generator.rb +50 -0
  22. data/lib/generators/seems_rateable/uninstall_old/templates/drop_seems_rateable_cached_ratings_table.rb +9 -0
  23. data/lib/generators/seems_rateable/uninstall_old/templates/drop_seems_rateable_rates_table.rb +9 -0
  24. data/lib/generators/seems_rateable/uninstall_old/uninstall_old_generator.rb +48 -0
  25. data/lib/seems_rateable.rb +22 -7
  26. data/lib/seems_rateable/builder.rb +35 -0
  27. data/lib/seems_rateable/builder/html_options.rb +37 -0
  28. data/lib/seems_rateable/configuration.rb +23 -0
  29. data/lib/seems_rateable/engine.rb +12 -7
  30. data/lib/seems_rateable/errors.rb +3 -17
  31. data/lib/seems_rateable/helpers/action_view_extension.rb +18 -0
  32. data/lib/seems_rateable/helpers/current_rater.rb +15 -0
  33. data/lib/seems_rateable/models/active_record_extension.rb +23 -0
  34. data/lib/seems_rateable/models/active_record_extension/rateable.rb +26 -0
  35. data/lib/seems_rateable/models/active_record_extension/rater.rb +11 -0
  36. data/lib/seems_rateable/rating.rb +19 -0
  37. data/lib/seems_rateable/routes.rb +1 -1
  38. data/lib/seems_rateable/version.rb +1 -1
  39. data/seems_rateable.gemspec +32 -0
  40. data/spec/builder_spec.rb +53 -0
  41. data/spec/controllers/rates_controller_spec.rb +56 -0
  42. data/spec/dummy/README.rdoc +28 -0
  43. data/spec/dummy/Rakefile +6 -0
  44. data/{app/assets/javascripts/seems_rateable → spec/dummy/app/assets/javascripts}/application.js +4 -3
  45. data/spec/dummy/app/assets/javascripts/posts.js +2 -0
  46. data/spec/dummy/app/assets/javascripts/rateable/rateable.js.erb +15 -0
  47. data/spec/dummy/app/assets/stylesheets/application.css +19 -0
  48. data/spec/dummy/app/assets/stylesheets/scaffold.css +56 -0
  49. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  50. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  51. data/spec/dummy/app/controllers/posts_controller.rb +58 -0
  52. data/spec/dummy/app/models/.keep +0 -0
  53. data/spec/dummy/app/models/concerns/.keep +0 -0
  54. data/spec/dummy/app/models/post.rb +3 -0
  55. data/spec/dummy/app/models/user.rb +5 -0
  56. data/spec/dummy/app/views/layouts/application.html.erb +26 -0
  57. data/spec/dummy/app/views/posts/_form.html.erb +21 -0
  58. data/spec/dummy/app/views/posts/edit.html.erb +6 -0
  59. data/spec/dummy/app/views/posts/index.html.erb +28 -0
  60. data/spec/dummy/app/views/posts/new.html.erb +5 -0
  61. data/spec/dummy/app/views/posts/show.html.erb +9 -0
  62. data/spec/dummy/bin/bundle +3 -0
  63. data/spec/dummy/bin/rails +4 -0
  64. data/spec/dummy/bin/rake +4 -0
  65. data/spec/dummy/config.ru +4 -0
  66. data/spec/dummy/config/application.rb +42 -0
  67. data/spec/dummy/config/boot.rb +5 -0
  68. data/spec/dummy/config/database.yml +25 -0
  69. data/spec/dummy/config/environment.rb +5 -0
  70. data/spec/dummy/config/environments/development.rb +29 -0
  71. data/spec/dummy/config/environments/production.rb +80 -0
  72. data/spec/dummy/config/environments/test.rb +36 -0
  73. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  74. data/spec/dummy/config/initializers/devise.rb +254 -0
  75. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  76. data/spec/dummy/config/initializers/inflections.rb +16 -0
  77. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  78. data/spec/dummy/config/initializers/secret_token.rb +12 -0
  79. data/spec/dummy/config/initializers/seems_rateable.rb +4 -0
  80. data/spec/dummy/config/initializers/session_store.rb +3 -0
  81. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  82. data/spec/dummy/config/locales/devise.en.yml +59 -0
  83. data/spec/dummy/config/locales/en.yml +23 -0
  84. data/spec/dummy/config/routes.rb +8 -0
  85. data/spec/dummy/db/migrate/20140506144141_create_posts.rb +9 -0
  86. data/spec/dummy/db/migrate/20140506144841_devise_create_users.rb +42 -0
  87. data/spec/dummy/db/migrate/20140520170316_create_seems_rateable_rates.rb +15 -0
  88. data/spec/dummy/db/schema.rb +54 -0
  89. data/spec/dummy/lib/assets/.keep +0 -0
  90. data/spec/dummy/public/404.html +58 -0
  91. data/spec/dummy/public/422.html +58 -0
  92. data/spec/dummy/public/500.html +57 -0
  93. data/spec/dummy/public/favicon.ico +0 -0
  94. data/spec/factories/posts.rb +5 -0
  95. data/spec/factories/rate.rb +7 -0
  96. data/spec/factories/users.rb +7 -0
  97. data/spec/features/rating_spec.rb +63 -0
  98. data/spec/helpers/action_view_extension_spec.rb +59 -0
  99. data/spec/models/active_record_extension_spec.rb +105 -0
  100. data/spec/models/rate_spec.rb +6 -0
  101. data/spec/rating_spec.rb +70 -0
  102. data/spec/spec_helper.rb +45 -0
  103. data/spec/support/dummies.rb +19 -0
  104. metadata +263 -25
  105. data/app/controllers/seems_rateable/ratings_controller.rb +0 -13
  106. data/app/models/seems_rateable/cached_rating.rb +0 -5
  107. data/lib/generators/seems_rateable/install/templates/cached_ratings_migration.rb +0 -17
  108. data/lib/seems_rateable/helpers.rb +0 -27
  109. data/lib/seems_rateable/model.rb +0 -91
@@ -0,0 +1,35 @@
1
+ require_relative 'builder/html_options'
2
+
3
+ module SeemsRateable
4
+ class Builder
5
+ include ActionView::Helpers::TagHelper
6
+
7
+ attr_reader :rateable, :options
8
+
9
+ def self.build(rateable, options)
10
+ new(rateable, options).build
11
+ end
12
+
13
+ def initialize(rateable, options)
14
+ @rateable, @options = rateable, options
15
+ end
16
+
17
+ def build
18
+ content_tag :div, nil, html_options
19
+ end
20
+
21
+ private
22
+
23
+ def html_options
24
+ HtmlOptions.new(rating, options).to_h
25
+ end
26
+
27
+ def rating
28
+ rateable.rating dimension
29
+ end
30
+
31
+ def dimension
32
+ options.delete(:dimension)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,37 @@
1
+ module SeemsRateable
2
+ class Builder
3
+ class HtmlOptions
4
+ attr_reader :rating, :options
5
+
6
+ def initialize(rating, options)
7
+ @rating, @options = rating, options
8
+ end
9
+
10
+ def to_h
11
+ options[:class] = "#{klass} #{disabled}".strip
12
+ options[:data] = options.fetch(:data, {}).merge(rating_attributes)
13
+ options
14
+ end
15
+
16
+ private
17
+
18
+ def rating_attributes
19
+ rate_params.merge average: rating.average
20
+ end
21
+
22
+ # Avoids passing nil dimension to the data html attrbitutes
23
+ # because it gets processed as data-dimension="null"
24
+ def rate_params
25
+ rating.rates.where_values_hash.keep_if { |k, v| v }
26
+ end
27
+
28
+ def klass
29
+ options[:class] || SeemsRateable.config.default_selector_class
30
+ end
31
+
32
+ def disabled
33
+ 'jDisabled' if options[:disabled]
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,23 @@
1
+ module SeemsRateable
2
+ def self.config
3
+ @config ||= Configuration.new
4
+ end
5
+
6
+ def self.configure(&block)
7
+ yield config if block_given?
8
+ end
9
+
10
+ class Configuration
11
+ include ActiveSupport::Configurable
12
+
13
+ config_accessor :rate_owner_class
14
+ config_accessor :current_rater_method
15
+ config_accessor :default_selector_class
16
+ end
17
+
18
+ configure do |config|
19
+ config.rate_owner_class = 'User'
20
+ config.current_rater_method = :current_user
21
+ config.default_selector_class = 'rateable'
22
+ end
23
+ end
@@ -2,15 +2,20 @@ module SeemsRateable
2
2
  class Engine < ::Rails::Engine
3
3
  isolate_namespace SeemsRateable
4
4
 
5
- config.generators do |g|
6
- g.test_framework :rspec, :fixture => false
7
- g.fixture_replacement :factory_girl, :dir => 'spec/factories'
8
- end
9
-
10
5
  initializer :seems_rateable do
11
- ActiveRecord::Base.send :include, SeemsRateable::Model
12
- ActionView::Base.send :include, SeemsRateable::Helpers
6
+ ::ActiveSupport.on_load(:active_record) { extend SeemsRateable::Models::ActiveRecordExtension }
7
+ ::ActiveSupport.on_load(:action_view) { include SeemsRateable::Helpers::ActionViewExtension }
8
+ ::ActiveSupport.on_load(:action_controller) { include SeemsRateable::Helpers::CurrentRater }
13
9
  ActionDispatch::Routing::Mapper.send :include, SeemsRateable::Routes
14
10
  end
11
+
12
+ config.after_initialize do
13
+ require 'seems_rateable/application_controller'
14
+ end
15
+
16
+ config.generators do |g|
17
+ g.test_framework :rspec, fixture: false
18
+ g.fixture_replacement :factory_girl, dir: 'spec/factories'
19
+ end
15
20
  end
16
21
  end
@@ -1,21 +1,7 @@
1
1
  module SeemsRateable
2
2
  module Errors
3
- class InvalidRateableObjectError < StandardError
4
- def to_s
5
- "Stated object is not rateable. Add 'seems_rateable' to your object's class model."
6
- end
7
- end
8
-
9
- class NoCurrentUserInstanceError < StandardError
10
- def to_s
11
- "User instance current_user is not available."
12
- end
13
- end
14
-
15
- class AlreadyRatedError < StandardError
16
- def to_s
17
- "User has already rated an object."
18
- end
19
- end
3
+ class InvalidRateableError < StandardError;end
4
+ class NonExistentDimension < StandardError;end
5
+ class NoCurrentRaterError < StandardError;end
20
6
  end
21
7
  end
@@ -0,0 +1,18 @@
1
+ module SeemsRateable
2
+ module Helpers
3
+ module ActionViewExtension
4
+ def rating_for(rateable, options={})
5
+ unless rateable.kind_of? Models::ActiveRecordExtension::Rateable
6
+ raise Errors::InvalidRateableError, "#{rateable.inspect} is not a rateable" +
7
+ " object, try adding 'seems_rateable' into your object's model"
8
+ end
9
+
10
+ if !current_rater || rateable.rated_by?(current_rater, options[:dimension])
11
+ options[:disabled] = true
12
+ end
13
+
14
+ SeemsRateable::Builder.build(rateable, options)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,15 @@
1
+ module SeemsRateable
2
+ module Helpers
3
+ module CurrentRater
4
+ extend ActiveSupport::Concern
5
+
6
+ def current_rater
7
+ send SeemsRateable.config.current_rater_method
8
+ end
9
+
10
+ included do
11
+ helper_method :current_rater
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,23 @@
1
+ module SeemsRateable
2
+ module Models
3
+ module ActiveRecordExtension
4
+ def seems_rateable(*dimensions)
5
+ has_many :_rates, -> { where dimension: nil }, as: :rateable, class_name: SeemsRateable::Rate, dependent: :destroy
6
+ has_many :_raters, through: :_rates, source: :rater, dependent: :destroy
7
+
8
+ dimensions.each do |dimension|
9
+ has_many :"#{dimension}_rates", -> { where dimension: dimension }, as: :rateable, class_name: SeemsRateable::Rate, dependent: :destroy
10
+ has_many :"#{dimension}_raters", through: :"#{dimension}_rates", source: :rater, dependent: :destroy
11
+ end
12
+
13
+ include Rateable
14
+ end
15
+
16
+ def seems_rateable_rater
17
+ has_many :rates_given, class_name: SeemsRateable::Rate, foreign_key: :rater_id
18
+
19
+ include Rater
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,26 @@
1
+ module SeemsRateable
2
+ module Models
3
+ module ActiveRecordExtension
4
+ module Rateable
5
+ %w[rates raters].each do |m|
6
+ define_method m do |dimension=nil|
7
+ if respond_to? "#{dimension}_#{m}"
8
+ send "#{dimension}_#{m}"
9
+ else
10
+ raise Errors::NonExistentDimension, "Dimension '#{dimension.inspect}' does not exist for #{self.class}"
11
+ end
12
+ end
13
+ end
14
+
15
+ def rating(dimension=nil)
16
+ SeemsRateable::Rating.new rates(dimension)
17
+ end
18
+
19
+ def rated_by?(rater, dimension=nil)
20
+ return unless rater
21
+ raters(dimension).include? rater
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,11 @@
1
+ module SeemsRateable
2
+ module Models
3
+ module ActiveRecordExtension
4
+ module Rater
5
+ def rates_given(dimension=nil)
6
+ super.where(dimension: dimension)
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,19 @@
1
+ module SeemsRateable
2
+ class Rating < Struct.new(:rates)
3
+ def average
4
+ sum/(count.nonzero? || 1)
5
+ end
6
+
7
+ def sum
8
+ stars.inject(:+).to_f
9
+ end
10
+
11
+ def count
12
+ stars.length
13
+ end
14
+
15
+ def stars
16
+ @stars ||= rates.pluck(:stars)
17
+ end
18
+ end
19
+ end
@@ -1,7 +1,7 @@
1
1
  module SeemsRateable
2
2
  module Routes
3
3
  def seems_rateable
4
- mount SeemsRateable::Engine => '/rateable', :as => :rateable
4
+ mount SeemsRateable::Engine => '/rateable', as: 'seems_rateable'
5
5
  end
6
6
  end
7
7
  end
@@ -1,3 +1,3 @@
1
1
  module SeemsRateable
2
- VERSION = "1.0.13"
2
+ VERSION = "2.0.0"
3
3
  end
@@ -0,0 +1,32 @@
1
+ $:.push File.expand_path("../lib", __FILE__)
2
+
3
+ require "seems_rateable/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "seems_rateable"
7
+ s.version = SeemsRateable::VERSION
8
+ s.authors = ["Peter Toth"]
9
+ s.email = ["peter@petertoth.me"]
10
+ s.homepage = "http://rateable.herokuapp.com"
11
+ s.summary = "Star Rating Engine"
12
+ s.description = "Star rating engine using jQuery plugin jRating for Rails applications"
13
+
14
+ s.files = `git ls-files`.split("\n")
15
+ s.test_files = `git ls-files -- spec/**/*`.split("\n")
16
+
17
+ s.add_dependency "rails", ">= 4.0.0"
18
+ s.add_dependency "jquery-rails"
19
+
20
+ s.add_development_dependency "pry-rails"
21
+ s.add_development_dependency "sqlite3"
22
+ s.add_development_dependency "devise"
23
+
24
+ s.add_development_dependency "rspec-rails"
25
+ s.add_development_dependency "factory_girl_rails"
26
+ s.add_development_dependency "database_cleaner"
27
+ s.add_development_dependency "capybara"
28
+ s.add_development_dependency "selenium-webdriver"
29
+ s.add_development_dependency "shoulda-matchers"
30
+ s.add_development_dependency "simplecov"
31
+ s.add_development_dependency "generator_spec"
32
+ end
@@ -0,0 +1,53 @@
1
+ require 'spec_helper'
2
+
3
+ feature SeemsRateable::Builder do
4
+ let(:rateable) { DummyModel.seems_rateable.create }
5
+ let(:options) { {} }
6
+
7
+ describe ".build" do
8
+ it "initializes a new builder object and builds it" do
9
+ SeemsRateable::Builder.should_receive(:new).with(rateable, kind_of(Hash)).and_call_original
10
+ SeemsRateable::Builder.any_instance.should_receive(:build)
11
+ SeemsRateable::Builder.build(rateable, options)
12
+ end
13
+ end
14
+
15
+ describe "#build" do
16
+ def rating(rateable, options)
17
+ Capybara.string(SeemsRateable::Builder.new(rateable, options).build).first('div')
18
+ end
19
+
20
+ subject { rating(rateable, options) }
21
+
22
+ describe "attributes" do
23
+ its(['class']) { should include(SeemsRateable.config.default_selector_class) }
24
+ its(['data-rateable-type']) { should eq(rateable.class.name) }
25
+ its(['data-rateable-id']) { should eq(rateable.id.to_s) }
26
+ its(['data-average']) { should_not be_nil }
27
+ its(['data-dimension']) { should be_nil }
28
+
29
+ context "with explicit class passed" do
30
+ let(:klass) { 'stuff' }
31
+
32
+ subject { rating(rateable, class: klass) }
33
+
34
+ its(['class']) { should include(klass) }
35
+ end
36
+
37
+ context "with dimension" do
38
+ let(:dimension) { 'quality' }
39
+ let(:rateable) { DummyModel.seems_rateable(dimension).create }
40
+
41
+ subject { rating(rateable, dimension: dimension) }
42
+
43
+ its(['data-dimension']) { should eq(dimension) }
44
+ end
45
+
46
+ context "when disabled" do
47
+ subject { rating(rateable, disabled: true) }
48
+
49
+ its(['class']) { should include('jDisabled') }
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,56 @@
1
+ require 'spec_helper'
2
+
3
+ describe SeemsRateable::RatesController do
4
+ routes { SeemsRateable::Engine.routes }
5
+
6
+ let(:user) { FactoryGirl.create(:user) }
7
+ let(:article) { FactoryGirl.create(:post) }
8
+
9
+ describe "POST #create" do
10
+ def create_rate
11
+ xhr :post, :create, valid_attributes(article)
12
+ end
13
+
14
+ def valid_attributes(rateable)
15
+ {
16
+ rate: {
17
+ rateable_type: rateable.class.name,
18
+ rateable_id: rateable.id,
19
+ stars: rand(1..5),
20
+ dimension: ['speed', 'quality', nil].sample
21
+ }
22
+ }
23
+ end
24
+
25
+ context "when valid" do
26
+ before do
27
+ controller.stub(:current_user).and_return(user)
28
+ end
29
+
30
+ it "creates a new rate" do
31
+ expect { create_rate }.to change(SeemsRateable::Rate, :count).by(1)
32
+ end
33
+
34
+ it "assigns a newly created rate as @rate" do
35
+ create_rate
36
+ assigns(:rate).should be_a(SeemsRateable::Rate)
37
+ assigns(:rate).should be_persisted
38
+ end
39
+
40
+ it "renders average rating as json" do
41
+ create_rate
42
+ expect(response.content_type).to eq('application/json')
43
+ expect(response.status).to eq(200)
44
+ expect(JSON.parse(response.body)).to have_key("average")
45
+ end
46
+ end
47
+
48
+ context "when invalid" do
49
+ context "when user is not signed in" do
50
+ it "raises a NoCurrentRaterError error" do
51
+ expect { create_rate }.to raise_error
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,28 @@
1
+ == README
2
+
3
+ This README would normally document whatever steps are necessary to get the
4
+ application up and running.
5
+
6
+ Things you may want to cover:
7
+
8
+ * Ruby version
9
+
10
+ * System dependencies
11
+
12
+ * Configuration
13
+
14
+ * Database creation
15
+
16
+ * Database initialization
17
+
18
+ * How to run the test suite
19
+
20
+ * Services (job queues, cache servers, search engines, etc.)
21
+
22
+ * Deployment instructions
23
+
24
+ * ...
25
+
26
+
27
+ Please feel free to use a different markup language if you do not plan to run
28
+ <tt>rake doc:app</tt>.