seems_rateable 1.0.13 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +20 -0
- data/Gemfile +3 -0
- data/README.md +41 -61
- data/Rakefile +8 -2
- data/app/assets/javascripts/seems_rateable/index.js +1 -0
- data/{lib/generators/seems_rateable/install/templates → app/assets/javascripts/seems_rateable}/jRating.js.erb +225 -225
- data/app/assets/stylesheets/seems_rateable/{application.css → index.css.erb} +2 -2
- data/app/controllers/seems_rateable/application_controller.rb +1 -3
- data/app/controllers/seems_rateable/rates_controller.rb +31 -0
- data/app/models/seems_rateable/rate.rb +2 -2
- data/bin/rails +8 -0
- data/config/routes.rb +1 -1
- data/lib/generators/seems_rateable/install/install_generator.rb +34 -28
- data/lib/generators/seems_rateable/install/templates/initializer.rb +5 -2
- data/lib/generators/seems_rateable/install/templates/rateable.js.erb +12 -22
- data/lib/generators/seems_rateable/install/templates/rates_migration.rb +6 -9
- data/lib/generators/seems_rateable/manifest_finder.rb +25 -0
- data/lib/generators/seems_rateable/migration_helpers.rb +21 -0
- data/lib/generators/seems_rateable/uninstall/templates/drop_seems_rateable_rates_table.rb +9 -0
- data/lib/generators/seems_rateable/uninstall/uninstall_generator.rb +50 -0
- data/lib/generators/seems_rateable/uninstall_old/templates/drop_seems_rateable_cached_ratings_table.rb +9 -0
- data/lib/generators/seems_rateable/uninstall_old/templates/drop_seems_rateable_rates_table.rb +9 -0
- data/lib/generators/seems_rateable/uninstall_old/uninstall_old_generator.rb +48 -0
- data/lib/seems_rateable.rb +22 -7
- data/lib/seems_rateable/builder.rb +35 -0
- data/lib/seems_rateable/builder/html_options.rb +37 -0
- data/lib/seems_rateable/configuration.rb +23 -0
- data/lib/seems_rateable/engine.rb +12 -7
- data/lib/seems_rateable/errors.rb +3 -17
- data/lib/seems_rateable/helpers/action_view_extension.rb +18 -0
- data/lib/seems_rateable/helpers/current_rater.rb +15 -0
- data/lib/seems_rateable/models/active_record_extension.rb +23 -0
- data/lib/seems_rateable/models/active_record_extension/rateable.rb +26 -0
- data/lib/seems_rateable/models/active_record_extension/rater.rb +11 -0
- data/lib/seems_rateable/rating.rb +19 -0
- data/lib/seems_rateable/routes.rb +1 -1
- data/lib/seems_rateable/version.rb +1 -1
- data/seems_rateable.gemspec +32 -0
- data/spec/builder_spec.rb +53 -0
- data/spec/controllers/rates_controller_spec.rb +56 -0
- data/spec/dummy/README.rdoc +28 -0
- data/spec/dummy/Rakefile +6 -0
- data/{app/assets/javascripts/seems_rateable → spec/dummy/app/assets/javascripts}/application.js +4 -3
- data/spec/dummy/app/assets/javascripts/posts.js +2 -0
- data/spec/dummy/app/assets/javascripts/rateable/rateable.js.erb +15 -0
- data/spec/dummy/app/assets/stylesheets/application.css +19 -0
- data/spec/dummy/app/assets/stylesheets/scaffold.css +56 -0
- data/spec/dummy/app/controllers/application_controller.rb +5 -0
- data/spec/dummy/app/controllers/concerns/.keep +0 -0
- data/spec/dummy/app/controllers/posts_controller.rb +58 -0
- data/spec/dummy/app/models/.keep +0 -0
- data/spec/dummy/app/models/concerns/.keep +0 -0
- data/spec/dummy/app/models/post.rb +3 -0
- data/spec/dummy/app/models/user.rb +5 -0
- data/spec/dummy/app/views/layouts/application.html.erb +26 -0
- data/spec/dummy/app/views/posts/_form.html.erb +21 -0
- data/spec/dummy/app/views/posts/edit.html.erb +6 -0
- data/spec/dummy/app/views/posts/index.html.erb +28 -0
- data/spec/dummy/app/views/posts/new.html.erb +5 -0
- data/spec/dummy/app/views/posts/show.html.erb +9 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +42 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +29 -0
- data/spec/dummy/config/environments/production.rb +80 -0
- data/spec/dummy/config/environments/test.rb +36 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/devise.rb +254 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +12 -0
- data/spec/dummy/config/initializers/seems_rateable.rb +4 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/devise.en.yml +59 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/routes.rb +8 -0
- data/spec/dummy/db/migrate/20140506144141_create_posts.rb +9 -0
- data/spec/dummy/db/migrate/20140506144841_devise_create_users.rb +42 -0
- data/spec/dummy/db/migrate/20140520170316_create_seems_rateable_rates.rb +15 -0
- data/spec/dummy/db/schema.rb +54 -0
- data/spec/dummy/lib/assets/.keep +0 -0
- data/spec/dummy/public/404.html +58 -0
- data/spec/dummy/public/422.html +58 -0
- data/spec/dummy/public/500.html +57 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/factories/posts.rb +5 -0
- data/spec/factories/rate.rb +7 -0
- data/spec/factories/users.rb +7 -0
- data/spec/features/rating_spec.rb +63 -0
- data/spec/helpers/action_view_extension_spec.rb +59 -0
- data/spec/models/active_record_extension_spec.rb +105 -0
- data/spec/models/rate_spec.rb +6 -0
- data/spec/rating_spec.rb +70 -0
- data/spec/spec_helper.rb +45 -0
- data/spec/support/dummies.rb +19 -0
- metadata +263 -25
- data/app/controllers/seems_rateable/ratings_controller.rb +0 -13
- data/app/models/seems_rateable/cached_rating.rb +0 -5
- data/lib/generators/seems_rateable/install/templates/cached_ratings_migration.rb +0 -17
- data/lib/seems_rateable/helpers.rb +0 -27
- 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
|
-
|
12
|
-
|
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
|
4
|
-
|
5
|
-
|
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,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,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
|
@@ -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>.
|