acts_as_favoritor 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.travis.yml +4 -0
  4. data/Gemfile +6 -0
  5. data/LICENSE +21 -0
  6. data/README.md +73 -0
  7. data/Rakefile +37 -0
  8. data/acts_as_favoritor.gemspec +29 -0
  9. data/init.rb +1 -0
  10. data/lib/acts_as_favoritor.rb +35 -0
  11. data/lib/acts_as_favoritor/favoritable.rb +115 -0
  12. data/lib/acts_as_favoritor/favorite_scopes.rb +45 -0
  13. data/lib/acts_as_favoritor/favoritor.rb +113 -0
  14. data/lib/acts_as_favoritor/favoritor_lib.rb +43 -0
  15. data/lib/acts_as_favoritor/railtie.rb +16 -0
  16. data/lib/acts_as_favoritor/version.rb +5 -0
  17. data/lib/generators/USAGE +5 -0
  18. data/lib/generators/acts_as_follower_generator.rb +28 -0
  19. data/lib/generators/templates/migration.rb.erb +18 -0
  20. data/lib/generators/templates/model.rb +16 -0
  21. data/test/README +24 -0
  22. data/test/acts_as_followable_test.rb +283 -0
  23. data/test/acts_as_follower_test.rb +224 -0
  24. data/test/dummy30/Gemfile +1 -0
  25. data/test/dummy30/Rakefile +7 -0
  26. data/test/dummy30/app/models/application_record.rb +3 -0
  27. data/test/dummy30/app/models/band.rb +4 -0
  28. data/test/dummy30/app/models/band/punk.rb +4 -0
  29. data/test/dummy30/app/models/band/punk/pop_punk.rb +4 -0
  30. data/test/dummy30/app/models/custom_record.rb +3 -0
  31. data/test/dummy30/app/models/some.rb +5 -0
  32. data/test/dummy30/app/models/user.rb +5 -0
  33. data/test/dummy30/config.ru +4 -0
  34. data/test/dummy30/config/application.rb +42 -0
  35. data/test/dummy30/config/boot.rb +10 -0
  36. data/test/dummy30/config/database.yml +6 -0
  37. data/test/dummy30/config/environment.rb +5 -0
  38. data/test/dummy30/config/environments/development.rb +18 -0
  39. data/test/dummy30/config/environments/test.rb +20 -0
  40. data/test/dummy30/config/initializers/backtrace_silencers.rb +7 -0
  41. data/test/dummy30/config/initializers/inflections.rb +10 -0
  42. data/test/dummy30/config/initializers/secret_token.rb +7 -0
  43. data/test/dummy30/config/initializers/session_store.rb +8 -0
  44. data/test/dummy30/config/locales/en.yml +5 -0
  45. data/test/dummy30/config/routes.rb +2 -0
  46. data/test/factories/bands.rb +17 -0
  47. data/test/factories/somes.rb +9 -0
  48. data/test/factories/users.rb +13 -0
  49. data/test/follow_test.rb +28 -0
  50. data/test/schema.rb +25 -0
  51. data/test/test_helper.rb +18 -0
  52. metadata +181 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: fa5428168c7b8609f60f208889cdc00039ec182e
4
+ data.tar.gz: 0c44c7a38d6a165069afa96cba0eeff74eee1d16
5
+ SHA512:
6
+ metadata.gz: 675ee97d8c2d37bd81627d738fa629eff5dff73558962b8e3e3862f4095ca420b278e000a066ad06cc8182d5d25a2143ba693f2a78e747b863b87d4c36d0a73a
7
+ data.tar.gz: 64e88864d50a179520792857dfcdb70f9b759f3097b92a23271f02b709d59bbd8d0722986872255613d2085f7a3de289aa4b13e6c34a05f15d1c4ecbcd273408
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ test/debug.log
2
+ coverage
3
+ rdoc
4
+ memory
5
+ *.gem
6
+ .rvmrc
7
+ Gemfile.lock
8
+ log
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.3.3
4
+ install: bundle install --without development --deployment --jobs=3 --retry=3
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gemspec
4
+
5
+
6
+ gem 'activesupport', ENV['RAILS_VERSION'] if ENV['RAILS_VERSION']
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2017 Slooob
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,73 @@
1
+ # acts_as_favoitor - Add Favorites to you Rails app
2
+
3
+ <img src="https://travis-ci.org/slooob/acts_as_favoritor.svg?branch=master" />
4
+
5
+ acts_as_favoritor is a Rubygem to allow any ActiveRecord model to favorite any other model. This is accomplished through a double polymorphic relationship on the Favorite model. There is also built in support for blocking/un-blocking favorite records.
6
+
7
+ ---
8
+
9
+ ## Installation
10
+
11
+ acts_as_favoritor works with Rails 4.0 onwards. You can add it to your `Gemfile` with:
12
+
13
+ ```ruby
14
+ gem 'acts_as_favoritor'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install acts_as_favoritor
24
+
25
+ If you always want to be up to date fetch the latest from GitHub in your `Gemfile`:
26
+
27
+ ```ruby
28
+ gem 'amphtml', github: 'slooob/acts_as_favoritor'
29
+ ```
30
+
31
+ Now run the generator:
32
+
33
+ $ rails g acts_as_favoritor
34
+
35
+ $ rails db:migrate
36
+
37
+ This will create a Favorite model as well as a migration file.
38
+
39
+ **Note:** Use `rake db:migrate` instead if you run Rails < 5.
40
+
41
+ ## Usage
42
+
43
+ ---
44
+
45
+ ## Contributors
46
+
47
+ Give the people some :heart: who are working on this project. Check them all at:
48
+
49
+ https://github.com/slooob/acts_as_favoritor/graphs/contributors
50
+
51
+ ## License
52
+
53
+ MIT License
54
+
55
+ Copyright (c) 2017 Jonas Hübotter
56
+
57
+ Permission is hereby granted, free of charge, to any person obtaining a copy
58
+ of this software and associated documentation files (the "Software"), to deal
59
+ in the Software without restriction, including without limitation the rights
60
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
61
+ copies of the Software, and to permit persons to whom the Software is
62
+ furnished to do so, subject to the following conditions:
63
+
64
+ The above copyright notice and this permission notice shall be included in all
65
+ copies or substantial portions of the Software.
66
+
67
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
68
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
69
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
70
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
71
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
72
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
73
+ SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,37 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rake'
5
+ require 'rake/testtask'
6
+ require 'rdoc/task'
7
+
8
+ desc 'Default: run unit tests.'
9
+ task default: :test
10
+
11
+ desc 'Test the acts_as_favoritor gem.'
12
+ Rake::TestTask.new(:test) do |t|
13
+ t.libs << 'lib'
14
+ t.pattern = 'test/**/*_test.rb'
15
+ t.verbose = true
16
+ end
17
+
18
+ desc 'Generate documentation for the acts_as_favoritor gem.'
19
+ Rake::RDocTask.new(:rdoc) do |rdoc|
20
+ rdoc.rdoc_dir = 'rdoc'
21
+ rdoc.title = 'Acts As Favoritor'
22
+ rdoc.main = 'README.rdoc'
23
+ rdoc.options << '--line-numbers' << '--inline-source'
24
+ rdoc.rdoc_files.include('README.rdoc', 'lib/**/*.rb')
25
+ end
26
+
27
+ namespace :rcov do
28
+ desc "Generate a coverage report in coverage/"
29
+ task :gen do
30
+ sh "rcov --output coverage test/*_test.rb --exclude 'gems/*'"
31
+ end
32
+
33
+ desc "Remove generated coverage files."
34
+ task :clobber do
35
+ sh "rm -rdf coverage"
36
+ end
37
+ end
@@ -0,0 +1,29 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path('../lib', __FILE__)
3
+ require 'acts_as_favoritor/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'acts_as_favoritor'
7
+ s.version = ActsAsFavoritor::VERSION
8
+ s.authors = ['Jonas Hübotter']
9
+ s.email = ['developer@slooob.com']
10
+ s.homepage = 'https://github.com/slooob/acts_as_favoritor'
11
+ s.summary = 'A Rubygem to add Favorite functionality for ActiveRecord models'
12
+ s.description = 'acts_as_favoritor is a Rubygem to allow any ActiveRecord model to favorite any other model. This is accomplished through a double polymorphic relationship on the Favorite model. There is also built in support for blocking/un-blocking favorite records.'
13
+ s.license = 'MIT'
14
+
15
+ s.rubyforge_project = 'acts_as_follower'
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ['lib']
21
+
22
+ s.add_dependency 'activerecord', '>= 4.0'
23
+
24
+ s.add_development_dependency 'sqlite3', '~> 1'
25
+ s.add_development_dependency 'shoulda_create', '~> 0'
26
+ s.add_development_dependency 'shoulda', '~> 3'
27
+ s.add_development_dependency 'factory_girl', '~> 4'
28
+ s.add_development_dependency 'rails', '>= 4.0'
29
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'acts_as_favoritor'
@@ -0,0 +1,35 @@
1
+ require 'acts_as_favoritor/version'
2
+
3
+ module ActsAsFavoritor
4
+
5
+ autoload :Favoritor, 'acts_as_favoritor/favoritor'
6
+ autoload :Favoritable, 'acts_as_favoritor/favoritable'
7
+ autoload :FavoritorLib, 'acts_as_favoritor/favoritor_lib'
8
+ autoload :FavoriteScopes, 'acts_as_favoritor/favorite_scopes'
9
+
10
+ def self.setup
11
+ @configuration ||= Configuration.new
12
+ yield @configuration if block_given?
13
+ end
14
+
15
+ def self.method_missing method_name, *args, &block
16
+ if method_name == :custom_parent_classes=
17
+ ActiveSupport::Deprecation.warn 'Setting custom parent classes is deprecated and will be removed in future versions.'
18
+ end
19
+ @configuration.respond_to?(method_name) ?
20
+ @configuration.send(method_name, *args, &block) : super
21
+ end
22
+
23
+ class Configuration
24
+ attr_accessor :custom_parent_classes
25
+
26
+ def initialize
27
+ @custom_parent_classes = []
28
+ end
29
+ end
30
+
31
+ setup
32
+
33
+ require 'favoritor/railtie' if defined?(Rails) && Rails::VERSION::MAJOR >= 3
34
+
35
+ end
@@ -0,0 +1,115 @@
1
+ module ActsAsFavoritor #:nodoc:
2
+ module Favoritable
3
+
4
+ def self.included base
5
+ base.extend ClassMethods
6
+ end
7
+
8
+ module ClassMethods
9
+ def acts_as_favoritable
10
+ has_many :favorited, as: :favoritable, dependent: :destroy, class_name: 'Favorite'
11
+ include ActsAsFavoritor::Favoritable::InstanceMethods
12
+ include ActsAsFavoritor::FavoritorLib
13
+ end
14
+ end
15
+
16
+ module InstanceMethods
17
+
18
+ # Returns the number of favoritors a record has.
19
+ def favoritors_count
20
+ self.favorited.unblocked.count
21
+ end
22
+
23
+ # Returns the favoritors by a given type
24
+ def favoritors_by_type favoritor_type, options = {}
25
+ favorites = favoritor_type.constantize.
26
+ joins(:favorites).
27
+ where('favorites.blocked': false,
28
+ 'favorites.followable_id': self.id,
29
+ 'favorites.followable_type': parent_class_name(self),
30
+ 'favorites.follower_type': favoritor_type)
31
+ if options.has_key? :limit
32
+ favorites = favorites.limit options[:limit]
33
+ end
34
+ if options.has_key? :includes
35
+ favorites = favorites.includes options[:includes]
36
+ end
37
+
38
+ favorites
39
+ end
40
+
41
+ def favoritors_by_type_count favoritor_type
42
+ self.favorited.unblocked.for_favoritor_type(favoritor_type).count
43
+ end
44
+
45
+ # Allows magic names on favoritors_by_type
46
+ # e.g. user_favoritors == favoritors_by_type 'User'
47
+ # Allows magic names on favoritors_by_type_count
48
+ # e.g. count_user_favoritors == favoritors_by_type_count 'User'
49
+ def method_missing m, *args
50
+ if m.to_s[/count_(.+)_favoritors/]
51
+ favoritors_by_type_count $1.singularize.classify
52
+ elsif m.to_s[/(.+)_favoritors/]
53
+ favoritors_by_type $1.singularize.classify
54
+ else
55
+ super
56
+ end
57
+ end
58
+
59
+ def respond_to? m, include_private = false
60
+ super || m.to_s[/count_(.+)_favoritors/] || m.to_s[/(.+)_favoritors/]
61
+ end
62
+
63
+ def blocked_favoritors_count
64
+ self.favorited.blocked.count
65
+ end
66
+
67
+ # Returns the favorited records scoped
68
+ def favoritors_scoped
69
+ self.favorited.includes :favoritor
70
+ end
71
+
72
+ def favoritors options = {}
73
+ favoritors_scope = favoritors_scoped.unblocked
74
+ favoritors_scope = apply_options_to_scope favoritors_scope, options
75
+ favoritors_scope.to_a.collect{ |f| f.favoritor }
76
+ end
77
+
78
+ def blocks options = {}
79
+ blocked_favoritors_scope = favoritors_scoped.blocked
80
+ blocked_favoritors_scope = apply_options_to_scope blocked_favoritors_scope, options
81
+ blocked_favoritors_scope.to_a.collect{ |f| f.favoritor }
82
+ end
83
+
84
+ # Returns true if the current instance is favorited by the passed record
85
+ # Returns false if the current instance is blocked by the passed record or no favorite is found
86
+ def favorited_by? favoritor
87
+ self.favorited.unblocked.for_favoritor(favoritor).first.present?
88
+ end
89
+
90
+ def block favoritor
91
+ get_favorite_for(favoritor) ? block_existing_favorite(favoritor) : block_future_favorite(favoritor)
92
+ end
93
+
94
+ def unblock favoritor
95
+ get_favorite_for(favoritor).try(:delete)
96
+ end
97
+
98
+ def get_favorite_for favoritor
99
+ self.favorited.for_favoritor(favoritor).first
100
+ end
101
+
102
+ private
103
+
104
+ def block_future_favorite favoritor
105
+ Favorite.create favoritable: self, favoritor: favoritor, blocked: true
106
+ end
107
+
108
+ def block_existing_favorite favoritor
109
+ get_favorite_for(favoritor).block!
110
+ end
111
+
112
+ end
113
+
114
+ end
115
+ end
@@ -0,0 +1,45 @@
1
+ module ActsAsFavoritor #:nodoc:
2
+ module FavoriteScopes
3
+
4
+ # returns favorite records where favoritor is the record passed in.
5
+ def for_favoritor favoritor
6
+ where favoritor_id: favoritor.id, favoritor_type: parent_class_name(favoritor)
7
+ end
8
+
9
+ # returns favorite records where favoritable is the record passed in.
10
+ def for_favoritable favoritable
11
+ where favoritable_id: favoritable.id, favoritable_type: parent_class_name(favoritable)
12
+ end
13
+
14
+ # returns favorite records where favoritor_type is the record passed in.
15
+ def for_favoritor_type favoritor_type
16
+ where favoritor_type: favoritor_type
17
+ end
18
+
19
+ # returns favorite records where favoritable_type is the record passed in.
20
+ def for_favoritable_type favoritable_type
21
+ where favoritable_type: favoritable_type
22
+ end
23
+
24
+ # returns favorite records from past 2 weeks with default parameter.
25
+ def recent from
26
+ where ['created_at > ?', (from || 2.weeks.ago).to_s(:db)]
27
+ end
28
+
29
+ # returns favorite records in descending order.
30
+ def descending
31
+ order 'favorites.created_at desc'
32
+ end
33
+
34
+ # returns unblocked favorite records.
35
+ def unblocked
36
+ where blocked: false
37
+ end
38
+
39
+ # returns blocked favorite records.
40
+ def blocked
41
+ where blocked: true
42
+ end
43
+
44
+ end
45
+ end
@@ -0,0 +1,113 @@
1
+ module ActsAsFavoritor #:nodoc:
2
+ module Follower
3
+
4
+ def self.included base
5
+ base.extend ClassMethods
6
+ end
7
+
8
+ module ClassMethods
9
+ def acts_as_favoritor
10
+ has_many :favorites, as: :favoritor, dependent: :destroy
11
+ include ActsAsFavoritor::Favoritor::InstanceMethods
12
+ include ActsAsFavoritor::FavoritorLib
13
+ end
14
+ end
15
+
16
+ module InstanceMethods
17
+
18
+ # Returns true if this instance is following the object passed as an argument.
19
+ def favorited? favoritable
20
+ 0 < Favorite.unblocked.for_favoritor(self).for_favoritable(favoritable).count
21
+ end
22
+
23
+ # Returns the number of objects this instance is following.
24
+ def favorites_count
25
+ Favorite.unblocked.for_favoritor(self).count
26
+ end
27
+
28
+ # Creates a new favorite record for this instance to favorite the passed object.
29
+ # Does not allow duplicate records to be created.
30
+ def favorite favoritable
31
+ if self != favoritable
32
+ params = {favoritable_id: favoritable.id, favoritable_type: parent_class_name(favoritable)}
33
+ self.favorites.where(params).first_or_create!
34
+ end
35
+ end
36
+
37
+ # Deletes the favorite record if it exists.
38
+ def remove_favorite favoritable
39
+ if favorite = get_Favoritor favoritable
40
+ favorite.destroy
41
+ end
42
+ end
43
+
44
+ # returns the favorite records to the current instance
45
+ def favorites_scoped
46
+ self.favorites.unblocked.includes :favoritable
47
+ end
48
+
49
+ # Returns the favorite records related to this instance by type.
50
+ def favorites_by_type favoritable_type, options={}
51
+ favorites_scope = favorites_scoped.for_favoritable_type favoritable_type
52
+ favorites_scope = apply_options_to_scope favorites_scope, options
53
+ end
54
+
55
+ # Returns the favorite records related to this instance with the favoritable included.
56
+ def all_favorites options = {}
57
+ favorites_scope = favorites_scoped
58
+ favorites_scope = apply_options_to_scope favorites_scope, options
59
+ end
60
+
61
+ # Returns the actual records which this instance has favorited.
62
+ def all_favorited options = {}
63
+ all_favorites(options).collect{ |f| f.favoritable }
64
+ end
65
+
66
+ # Returns the actual records of a particular type which this record has fovarited.
67
+ def favorited_by_type favoritable_type, options = {}
68
+ favoritables = favoritable_type.constantize.
69
+ joins(:favorited).
70
+ where('favorites.blocked': false,
71
+ 'favorites.favoritor_id': self.id,
72
+ 'favorites.favoritor_type': parent_class_name(self),
73
+ 'favorites.favoritable_type': favoritable_type)
74
+ if options.has_key? :limit
75
+ favoritables = favoritables.limit options[:limit]
76
+ end
77
+ if options.has_key? :includes
78
+ favoritables = favoritables.includes options[:includes]
79
+ end
80
+ favoritables
81
+ end
82
+
83
+ def favorited_by_type_count favoritable_type
84
+ favorites.unblocked.for_favoritable_type(favoritable_type).count
85
+ end
86
+
87
+ # Allows magic names on favorited_by_type
88
+ # e.g. favorited_users == favorited_by_type 'User'
89
+ # Allows magic names on favorited_by_type_count
90
+ # e.g. favorited_users_count == favorited_by_type_count 'User'
91
+ def method_missing m, *args
92
+ if m.to_s[/favorited_(.+)_count/]
93
+ favorited_by_type_count $1.singularize.classify
94
+ elsif m.to_s[/favorited_(.+)/]
95
+ favorited_by_type $1.singularize.classify
96
+ else
97
+ super
98
+ end
99
+ end
100
+
101
+ def respond_to? m, include_private = false
102
+ super || m.to_s[/favorited_(.+)_count/] || m.to_s[/favorited_(.+)/]
103
+ end
104
+
105
+ # Returns a favorite record for the current instance and favoritable object.
106
+ def get_favorite favoritable
107
+ self.favorites.unblocked.for_favoritable(favoritable).first
108
+ end
109
+
110
+ end
111
+
112
+ end
113
+ end