followability 0.1.0 → 1.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 96a742ecd47a5d0cc1f9c5247d991f5da15540fe32f4ed1f5cb340ff42f75560
4
- data.tar.gz: d3f37786e80e7d1adbc6e74de84b43f9c2e923208cc3673d3557a2b3baa73f8c
3
+ metadata.gz: 5381804ee6dcba9d0a7fcd76d2a560e9b6ff371aa3b2c4212cb394f8abbf4468
4
+ data.tar.gz: 4aae8e97665f8b10f1a1ee31d9b244d1a63fed320d7e9777249c907c215e3d5f
5
5
  SHA512:
6
- metadata.gz: 990e5646cb61a4300be524bb48e875012daa5faff1362ebf9ba6ed2547a0b66cb323b27e4e882e30a486986750ae201ec6ad858dcf8f35c34a390fb2f07400c6
7
- data.tar.gz: 1901bab6ec5670d0337357789fb2e8d82192a469d00e7d5345970e21269435281df397fbd50cf39fdce7b98996eb83e5947c5fe1d5302215b83b1ac3a7f0e4e3
6
+ metadata.gz: db1fab64911111ec2ef69ca06f4b173f4bd0daf4e49085fcbaae2674c6b1efac9e549ffe492ab2205eee0bc413200c09910094fa4819b914cb92a0bfbe1a6fa5
7
+ data.tar.gz: fb8e8e23c3b63158ba16e7981d85de9ee0382ee530d3b0de2194feb125e6d6ba5ed209c1c5caa12476814701e50038bd906c1b70fb19b2cc1c808d0b526636d2
data/.rubocop.yml CHANGED
@@ -20,4 +20,10 @@ Style/Documentation:
20
20
  Metrics/MethodLength:
21
21
  Enabled: true
22
22
  CountComments: false
23
- Max: 20
23
+ Max: 30
24
+
25
+ Metrics/AbcSize:
26
+ Max: 30
27
+
28
+ Metrics/BlockLength:
29
+ Max: 30
data/CHANGELOG.md CHANGED
@@ -1,4 +1,16 @@
1
- ## [Unreleased]
1
+ ## [1.0.0] - 2022-10-15
2
+ - Added belowing methods
3
+ - decline_follow_request_of
4
+ - remove_follow_request_for
5
+ - send_follow_request_to
6
+ - following?
7
+ - mutual_following_with?
8
+ - sent_follow_request_to?
9
+ - block_to
10
+ - unblock_to
11
+ - blocked?
12
+ - blocked_by?
13
+ - myself?
2
14
 
3
15
  ## [0.1.0] - 2022-10-14
4
16
 
data/Gemfile CHANGED
@@ -25,3 +25,15 @@ gem 'dry-configurable', '~> 0.15.0'
25
25
 
26
26
  # Pry is a runtime developer console and IRB alternative with powerful introspection capabilities [https://github.com/pry/pry]
27
27
  gem 'pry', '~> 0.14.1'
28
+
29
+ # A toolkit of support libraries and Ruby core extensions extracted from the Rails framework [https://github.com/rails/rails]
30
+ gem 'activesupport', '>= 5.0', '>= 7.0.4'
31
+
32
+ # A toolkit for building modeling frameworks like Active Record [https://github.com/rails/rails]
33
+ gem 'activemodel', '>= 5.0', '>= 7.0.4'
34
+
35
+ # Databases on Rails. Build a persistent domain model by mapping database tables to Ruby classes [https://github.com/rails/rails]
36
+ gem 'activerecord', '>= 5.0', '>= 7.0.4'
37
+
38
+ # Ruby on Rails is a full-stack web framework optimized for programmer happiness and sustainable productivity [https://github.com/rails/rails]
39
+ gem 'rails', '>= 5.0', '>= 7.0.4'
data/Gemfile.lock CHANGED
@@ -1,15 +1,82 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- followability (0.1.0)
4
+ followability (1.0.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
+ actioncable (7.0.4)
10
+ actionpack (= 7.0.4)
11
+ activesupport (= 7.0.4)
12
+ nio4r (~> 2.0)
13
+ websocket-driver (>= 0.6.1)
14
+ actionmailbox (7.0.4)
15
+ actionpack (= 7.0.4)
16
+ activejob (= 7.0.4)
17
+ activerecord (= 7.0.4)
18
+ activestorage (= 7.0.4)
19
+ activesupport (= 7.0.4)
20
+ mail (>= 2.7.1)
21
+ net-imap
22
+ net-pop
23
+ net-smtp
24
+ actionmailer (7.0.4)
25
+ actionpack (= 7.0.4)
26
+ actionview (= 7.0.4)
27
+ activejob (= 7.0.4)
28
+ activesupport (= 7.0.4)
29
+ mail (~> 2.5, >= 2.5.4)
30
+ net-imap
31
+ net-pop
32
+ net-smtp
33
+ rails-dom-testing (~> 2.0)
34
+ actionpack (7.0.4)
35
+ actionview (= 7.0.4)
36
+ activesupport (= 7.0.4)
37
+ rack (~> 2.0, >= 2.2.0)
38
+ rack-test (>= 0.6.3)
39
+ rails-dom-testing (~> 2.0)
40
+ rails-html-sanitizer (~> 1.0, >= 1.2.0)
41
+ actiontext (7.0.4)
42
+ actionpack (= 7.0.4)
43
+ activerecord (= 7.0.4)
44
+ activestorage (= 7.0.4)
45
+ activesupport (= 7.0.4)
46
+ globalid (>= 0.6.0)
47
+ nokogiri (>= 1.8.5)
48
+ actionview (7.0.4)
49
+ activesupport (= 7.0.4)
50
+ builder (~> 3.1)
51
+ erubi (~> 1.4)
52
+ rails-dom-testing (~> 2.0)
53
+ rails-html-sanitizer (~> 1.1, >= 1.2.0)
54
+ activejob (7.0.4)
55
+ activesupport (= 7.0.4)
56
+ globalid (>= 0.3.6)
57
+ activemodel (7.0.4)
58
+ activesupport (= 7.0.4)
59
+ activerecord (7.0.4)
60
+ activemodel (= 7.0.4)
61
+ activesupport (= 7.0.4)
62
+ activestorage (7.0.4)
63
+ actionpack (= 7.0.4)
64
+ activejob (= 7.0.4)
65
+ activerecord (= 7.0.4)
66
+ activesupport (= 7.0.4)
67
+ marcel (~> 1.0)
68
+ mini_mime (>= 1.1.0)
69
+ activesupport (7.0.4)
70
+ concurrent-ruby (~> 1.0, >= 1.0.2)
71
+ i18n (>= 1.6, < 2)
72
+ minitest (>= 5.1)
73
+ tzinfo (~> 2.0)
9
74
  ast (2.4.2)
10
75
  awesome_print (1.9.2)
76
+ builder (3.2.4)
11
77
  coderay (1.1.3)
12
78
  concurrent-ruby (1.1.10)
79
+ crass (1.0.6)
13
80
  dry-configurable (0.15.0)
14
81
  concurrent-ruby (~> 1.0)
15
82
  dry-core (~> 0.6)
@@ -18,15 +85,68 @@ GEM
18
85
  dry-monads (1.4.0)
19
86
  concurrent-ruby (~> 1.0)
20
87
  dry-core (~> 0.7)
88
+ erubi (1.11.0)
89
+ globalid (1.0.0)
90
+ activesupport (>= 5.0)
91
+ i18n (1.12.0)
92
+ concurrent-ruby (~> 1.0)
21
93
  json (2.6.2)
94
+ loofah (2.19.0)
95
+ crass (~> 1.0.2)
96
+ nokogiri (>= 1.5.9)
97
+ mail (2.7.1)
98
+ mini_mime (>= 0.1.1)
99
+ marcel (1.0.2)
22
100
  method_source (1.0.0)
101
+ mini_mime (1.1.2)
23
102
  minitest (5.16.3)
103
+ net-imap (0.3.1)
104
+ net-protocol
105
+ net-pop (0.1.2)
106
+ net-protocol
107
+ net-protocol (0.1.3)
108
+ timeout
109
+ net-smtp (0.3.2)
110
+ net-protocol
111
+ nio4r (2.5.8)
112
+ nokogiri (1.13.8-arm64-darwin)
113
+ racc (~> 1.4)
24
114
  parallel (1.22.1)
25
115
  parser (3.1.2.1)
26
116
  ast (~> 2.4.1)
27
117
  pry (0.14.1)
28
118
  coderay (~> 1.1)
29
119
  method_source (~> 1.0)
120
+ racc (1.6.0)
121
+ rack (2.2.4)
122
+ rack-test (2.0.2)
123
+ rack (>= 1.3)
124
+ rails (7.0.4)
125
+ actioncable (= 7.0.4)
126
+ actionmailbox (= 7.0.4)
127
+ actionmailer (= 7.0.4)
128
+ actionpack (= 7.0.4)
129
+ actiontext (= 7.0.4)
130
+ actionview (= 7.0.4)
131
+ activejob (= 7.0.4)
132
+ activemodel (= 7.0.4)
133
+ activerecord (= 7.0.4)
134
+ activestorage (= 7.0.4)
135
+ activesupport (= 7.0.4)
136
+ bundler (>= 1.15.0)
137
+ railties (= 7.0.4)
138
+ rails-dom-testing (2.0.3)
139
+ activesupport (>= 4.2.0)
140
+ nokogiri (>= 1.6)
141
+ rails-html-sanitizer (1.4.3)
142
+ loofah (~> 2.3)
143
+ railties (7.0.4)
144
+ actionpack (= 7.0.4)
145
+ activesupport (= 7.0.4)
146
+ method_source
147
+ rake (>= 12.2)
148
+ thor (~> 1.0)
149
+ zeitwerk (~> 2.5)
30
150
  rainbow (3.1.1)
31
151
  rake (13.0.6)
32
152
  regexp_parser (2.6.0)
@@ -44,18 +164,30 @@ GEM
44
164
  rubocop-ast (1.21.0)
45
165
  parser (>= 3.1.1.0)
46
166
  ruby-progressbar (1.11.0)
167
+ thor (1.2.1)
168
+ timeout (0.3.0)
169
+ tzinfo (2.0.5)
170
+ concurrent-ruby (~> 1.0)
47
171
  unicode-display_width (2.3.0)
172
+ websocket-driver (0.7.5)
173
+ websocket-extensions (>= 0.1.0)
174
+ websocket-extensions (0.1.5)
175
+ zeitwerk (2.6.1)
48
176
 
49
177
  PLATFORMS
50
178
  arm64-darwin-21
51
179
 
52
180
  DEPENDENCIES
181
+ activemodel (>= 7.0.4, >= 5.0)
182
+ activerecord (>= 7.0.4, >= 5.0)
183
+ activesupport (>= 7.0.4, >= 5.0)
53
184
  awesome_print
54
185
  dry-configurable (~> 0.15.0)
55
186
  dry-monads (~> 1.4)
56
187
  followability!
57
188
  minitest (~> 5.0)
58
189
  pry (~> 0.14.1)
190
+ rails (>= 7.0.4, >= 5.0)
59
191
  rake (~> 13.0)
60
192
  rubocop (~> 1.21)
61
193
 
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- [![Gem Version](https://badge.fury.io/rb/btc_turk.svg)](https://badge.fury.io/rb/btc_turk)
1
+ [![Gem Version](https://badge.fury.io/rb/followability.svg)](https://badge.fury.io/rb/followability)
2
2
  ![test](https://github.com/nejdetkadir/followability/actions/workflows/test.yml/badge.svg?branch=main)
3
3
  ![rubocop](https://github.com/nejdetkadir/followability/actions/workflows/rubocop.yml/badge.svg?branch=main)
4
4
  [![Ruby Style Guide](https://img.shields.io/badge/code_style-rubocop-brightgreen.svg)](https://github.com/rubocop/rubocop)
@@ -13,15 +13,233 @@ gem 'followability', github: 'nejdetkadir/followability', branch: 'main'
13
13
  ```
14
14
 
15
15
  Install the gem and add to the application's Gemfile by executing:
16
-
17
- $ bundle add followability
16
+ ```bash
17
+ $ bundle add followability
18
+ ```
18
19
 
19
20
  If bundler is not being used to manage dependencies, install the gem by executing:
21
+ ```bash
22
+ $ gem install followability
23
+ ```
20
24
 
21
- $ gem install followability
25
+ Run the generator for creating database migration and copying localization files.
26
+ ```bash
27
+ $ rails g followability:install
28
+ ```
22
29
 
23
30
  ## Usage
24
- TODO: Write usage instructions here
31
+ Simply drop in `followability` to a model:
32
+
33
+ ```ruby
34
+ class User < ActiveRecord::Base
35
+ followability
36
+ end
37
+ ```
38
+
39
+ Now, instances of `User` have followability.
40
+ ```ruby
41
+ User.followability?
42
+ # => true
43
+ ```
44
+
45
+ ### Following actions
46
+ Avaiable methods:
47
+ - decline_follow_request_of
48
+ - remove_follow_request_for
49
+ - send_follow_request_to
50
+ - unfollow
51
+ - following?
52
+ - mutual_following_with?
53
+ - sent_follow_request_to?
54
+
55
+ ### Usage
56
+ ```ruby
57
+ @foo = User.first
58
+ @bar = User.last
59
+
60
+ @foo.send_follow_request_to(@bar)
61
+ # => true
62
+
63
+ @foo.sent_follow_request_to?(@bar)
64
+ # => true
65
+
66
+ @bar.decline_follow_request_of(@foo)
67
+ # => true
68
+
69
+ @bar.accept_follow_request_of(@foo)
70
+ # => false
71
+
72
+ @bar.errors.full_messages
73
+ # => [...]
74
+
75
+ @foo.remove_follow_request_for(@bar)
76
+ # => false
77
+
78
+ @foo.errors.full_messages
79
+ # => [...]
80
+
81
+ @foo.mutual_following_with?(@bar)
82
+ # => false
83
+
84
+ @bar.following?(@foo)
85
+ # => false
86
+ ```
87
+
88
+ ### Blocking actions
89
+ Avaiable methods:
90
+ - block
91
+ - unblock
92
+ - blocked?
93
+ - blocked_by?
94
+
95
+ ### Usage
96
+ ```ruby
97
+ @foo.block(@bar)
98
+ # => true
99
+
100
+ @foo.blocked?(@bar)
101
+ # => true
102
+
103
+ @bar.blocked_by?(@foo)
104
+ # => true
105
+
106
+ @foo.unblock(@bar)
107
+ # => true
108
+ ```
109
+
110
+ ### Common
111
+ Avaiable methods:
112
+ - myself?
113
+
114
+ ### Usage
115
+ ```ruby
116
+ class User < ActiveRecord::Base
117
+ followability
118
+
119
+ def follow_request_removed_by_someone(record)
120
+ unless myself?(record)
121
+ # Do something
122
+ end
123
+ end
124
+ end
125
+ ```
126
+
127
+ ### Relations
128
+ Avaiable methods:
129
+ - follow_requests
130
+ - pending_requests
131
+ - followerable_relationships
132
+ - followable_relationships
133
+ - followers
134
+ - following
135
+ - blocks
136
+
137
+ ### Usage
138
+ ```ruby
139
+ @foo.follow_requests
140
+ # => [#<Followability::Relationship ...>]
141
+
142
+ @foo.pending_requests
143
+ # => [#<Followability::Relationship ...>]
144
+
145
+ @foo.followerable_relationships
146
+ # => [#<Followability::Relationship ...>]
147
+
148
+ @foo.followable_relationships
149
+ # => [#<Followability::Relationship ...>]
150
+
151
+ @foo.followers
152
+ # => [#<User ...>]
153
+
154
+ @foo.following
155
+ # => [#<User ...>]
156
+
157
+ @foo.blocks
158
+ # => [#<User ...>]
159
+ ```
160
+
161
+ ### Callback Methods
162
+ Available methods:
163
+ - follow_request_sent_to_me
164
+ - follow_request_sent_to_someone
165
+ - follow_request_accepted_by_me
166
+ - follow_request_accepted_by_someone
167
+ - follow_request_declined_by_me
168
+ - follow_request_declined_by_someone
169
+ - follow_request_removed_by_me
170
+ - follow_request_removed_by_someone
171
+ - followable_blocked_by_me
172
+ - followable_blocked_by_someone
173
+ - followable_unblocked_by_me
174
+ - followable_unblocked_by_someone
175
+ - unfollow_by_me
176
+ - unfollow_by_someone
177
+ - followability_triggered
178
+
179
+ ### Usage
180
+ ```ruby
181
+ class User < ActiveRecord::Base
182
+ followability
183
+
184
+ def follow_request_sent_to_me(record)
185
+ Notifications::FollowRequestSentToMeJob.perform_later(from_id: record.id)
186
+ end
187
+
188
+ def follow_request_sent_to_someone(record); end
189
+ def follow_request_accepted_by_me(record); end
190
+ def follow_request_accepted_by_someone(record); end
191
+ def follow_request_declined_by_me(record); end
192
+ def follow_request_declined_by_someone(record); end
193
+ def follow_request_removed_by_me(record); end
194
+ def follow_request_removed_by_someone(record); end
195
+ def followable_blocked_by_me(record); end
196
+ def followable_blocked_by_someone(record); end
197
+ def followable_unblocked_by_me(record); end
198
+ def followable_unblocked_by_someone(record); end
199
+ def unfollow_by_me(record); end
200
+
201
+ def unfollow_by_someone(record)
202
+ Followability::RemoveFollowedUser(user_id: record.id)
203
+ end
204
+
205
+ def followability_triggered(record, callback_name); end
206
+ end
207
+ ```
208
+
209
+ ## I18n
210
+ ```yml
211
+ ---
212
+ en:
213
+ followability:
214
+ errors:
215
+ block:
216
+ unblock:
217
+ myself: 'You can not run this action for yourself'
218
+ block:
219
+ myself: 'You can not run this action for yourself'
220
+ blocked_by: 'You can not block to who blocked to you'
221
+ already_blocked: '%{klass} already blocked'
222
+ not_blocked_for_blocking: 'You can not unblock to %{klass} because was not blocked'
223
+ follow:
224
+ unfollow:
225
+ myself: 'You can not run this action for yourself'
226
+ empty_relation: 'You can not unfollow to %{klass} because was not followed'
227
+ decline_follow_request_of:
228
+ myself: 'You can not run this action for yourself'
229
+ empty_relation: 'You can not decline follow request of %{klass} because was not sent'
230
+ accept_follow_request_of:
231
+ myself: 'You can not run this action for yourself'
232
+ empty_relation: 'You can not accept follow request of %{klass} because was not sent'
233
+ remove_follow_request_for:
234
+ empty_relation: 'You can not remove follow request of %{klass} because was not sent'
235
+ myself: 'You can not run this action for yourself'
236
+ send_follow_request_to:
237
+ myself: 'You can not run this action for yourself'
238
+ blocked_by: 'You can not send follow request to who blocked to you'
239
+ following: 'You are already following to %{klass}'
240
+ already_sent: 'You are already sent follow request'
241
+ blocked: 'You can not send follow request to blocked %{klass}'
242
+ ```
25
243
 
26
244
  ## Development
27
245
 
@@ -0,0 +1,31 @@
1
+ ---
2
+ en:
3
+ followability:
4
+ errors:
5
+ block:
6
+ unblock:
7
+ myself: 'You can not run this action for yourself'
8
+ block:
9
+ myself: 'You can not run this action for yourself'
10
+ blocked_by: 'You can not block to who blocked to you'
11
+ already_blocked: '%{klass} already blocked'
12
+ not_blocked_for_blocking: 'You can not unblock to %{klass} because was not blocked'
13
+ follow:
14
+ unfollow:
15
+ myself: 'You can not run this action for yourself'
16
+ empty_relation: 'You can not unfollow to %{klass} because was not followed'
17
+ decline_follow_request_of:
18
+ myself: 'You can not run this action for yourself'
19
+ empty_relation: 'You can not decline follow request of %{klass} because was not sent'
20
+ accept_follow_request_of:
21
+ myself: 'You can not run this action for yourself'
22
+ empty_relation: 'You can not accept follow request of %{klass} because was not sent'
23
+ remove_follow_request_for:
24
+ empty_relation: 'You can not remove follow request of %{klass} because was not sent'
25
+ myself: 'You can not run this action for yourself'
26
+ send_follow_request_to:
27
+ myself: 'You can not run this action for yourself'
28
+ blocked_by: 'You can not send follow request to who blocked to you'
29
+ following: 'You are already following to %{klass}'
30
+ already_sent: 'You are already sent follow request'
31
+ blocked: 'You can not send follow request to blocked %{klass}'
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Followability
4
+ module Followable
5
+ module Actions
6
+ module Block
7
+ I18N_SCOPE = 'followability.errors.block'
8
+
9
+ def block(record)
10
+ if myself?(record)
11
+ errors.add(:base, I18n.t('block.myself', scope: I18N_SCOPE, klass: record.class))
12
+
13
+ return false
14
+ end
15
+
16
+ if blocked_by?(record)
17
+ errors.add(:base, I18n.t('block.blocked_by', scope: I18N_SCOPE, klass: record.class))
18
+
19
+ return false
20
+ end
21
+
22
+ if blocked?(record)
23
+ errors.add(:base, I18n.t('block.already_blocked', scope: I18N_SCOPE, klass: record.class))
24
+
25
+ return false
26
+ end
27
+
28
+ relation = followerable_relationships.blocked.new(followable: record,
29
+ status: Followability::Relationship.statuses[:blocked])
30
+
31
+ if relation.save
32
+ run_callback(self, affected: record, callback: :followable_blocked_by_me)
33
+ run_callback(record, affected: self, callback: :followable_blocked_by_someone)
34
+
35
+ true
36
+ else
37
+ errors.add(:base, relation.errors.full_messages.to_sentence)
38
+
39
+ false
40
+ end
41
+ end
42
+
43
+ def unblock(record)
44
+ if myself?(record)
45
+ errors.add(:base, I18n.t('unblock.myself', scope: I18N_SCOPE, klass: record.class))
46
+
47
+ return false
48
+ end
49
+
50
+ unless blocked?(record)
51
+ errors.add(:base, I18n.t(:not_blocked_for_blocking, scope: I18N_SCOPE, klass: record.class))
52
+
53
+ return false
54
+ end
55
+
56
+ relation = followerable_relationships.blocked.find_by(followable: record)
57
+
58
+ if relation.destroy
59
+ run_callback(self, affected: record, callback: :followable_unblocked_by_me)
60
+ run_callback(record, affected: self, callback: :followable_unblocked_by_someone)
61
+
62
+ true
63
+ else
64
+ errors.add(:base, relation.errors.full_messages.to_sentence)
65
+
66
+ false
67
+ end
68
+ end
69
+
70
+ def blocked?(record)
71
+ followerable_relationships.blocked.exists?(followable: record)
72
+ end
73
+
74
+ def blocked_by?(record)
75
+ record.followerable_relationships.blocked.exists?(followable: self)
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Followability
4
+ module Followable
5
+ module Actions
6
+ module Common
7
+ def myself?(record)
8
+ id == record.id
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,183 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Metrics/ModuleLength
4
+ module Followability
5
+ module Followable
6
+ module Actions
7
+ module Follow
8
+ I18N_SCOPE = 'followability.errors.follow'
9
+
10
+ def decline_follow_request_of(record)
11
+ if myself?(record)
12
+ errors.add(:base, I18n.t('decline_follow_request_of.myself', scope: I18N_SCOPE, klass: record.class))
13
+
14
+ return false
15
+ end
16
+
17
+ relation = follow_requests.find_by(followerable_id: record.id, followerable_type: record.class.name)
18
+
19
+ if relation.blank?
20
+ errors.add(:base,
21
+ I18n.t('decline_follow_request_of.empty_relation', scope: I18N_SCOPE, klass: record.class.name))
22
+
23
+ false
24
+ elsif relation.destroy
25
+ run_callback(self, affected: record, callback: :follow_request_declined_by_me)
26
+ run_callback(record, affected: self, callback: :follow_request_declined_by_someone)
27
+
28
+ true
29
+ else
30
+ errors.add(:base, relation.errors.full_messages.to_sentence)
31
+
32
+ false
33
+ end
34
+ end
35
+
36
+ def unfollow(record)
37
+ if myself?(record)
38
+ errors.add(:base, I18n.t('unfollow.myself', scope: I18N_SCOPE, klass: record.class))
39
+
40
+ return false
41
+ end
42
+
43
+ unless following?(record)
44
+ errors.add(:base, I18n.t('unfollow.not_following', scope: I18N_SCOPE, klass: record.class))
45
+
46
+ return false
47
+ end
48
+
49
+ relation = followerable_relationships.find_by(followable_id: record.id,
50
+ followable_type: record.class.name,
51
+ status: Followable::Relationship.statuses[:following])
52
+
53
+ if relation.destroy
54
+ run_callback(self, affected: record, callback: :unfollow_by_me)
55
+ run_callback(record, affected: self, callback: :unfollow_by_someone)
56
+
57
+ true
58
+ else
59
+ errors.add(:base, relation.errors.full_messages)
60
+
61
+ false
62
+ end
63
+ end
64
+
65
+ def accept_follow_request_of(record)
66
+ if myself?(record)
67
+ errors.add(:base, I18n.t('accept_follow_request_of.myself', scope: I18N_SCOPE, klass: record.class))
68
+
69
+ return false
70
+ end
71
+
72
+ relation = follow_requests.find_by(followerable_id: record.id, followerable_type: record.class.name)
73
+
74
+ if relation.blank?
75
+ errors.add(:base,
76
+ I18n.t('accept_follow_request_of.empty_relation', scope: I18N_SCOPE, klass: record.class.name))
77
+
78
+ false
79
+ elsif relation.update(status: Followability::Relationship.statuses[:following])
80
+ run_callback(record, affected: record, callback: :follow_request_accepted_by_someone)
81
+ run_callback(self, affected: self, callback: :follow_request_accepted_by_me)
82
+
83
+ true
84
+ else
85
+ errors.add(:base, relation.errors.full_messages.to_sentence)
86
+
87
+ false
88
+ end
89
+ end
90
+
91
+ def remove_follow_request_for(record)
92
+ if myself?(record)
93
+ errors.add(:base, I18n.t('remove_follow_request_for.myself', scope: I18N_SCOPE, klass: record.class))
94
+
95
+ return false
96
+ end
97
+
98
+ relation = pending_requests.find_by(followable_id: record.id, followable_type: record.class.name)
99
+
100
+ if relation.blank?
101
+ errors.add(:base,
102
+ I18n.t('remove_follow_request_for.empty_relation', scope: I18N_SCOPE, klass: record.class.name))
103
+
104
+ false
105
+ elsif relation.destroy
106
+ run_callback(self, affected: record, callback: :follow_request_removed_by_me)
107
+ run_callback(record, affected: self, callback: :follow_request_removed_by_someone)
108
+
109
+ true
110
+ else
111
+ errors.add(:base, relation.errors.full_messages.to_sentence)
112
+
113
+ false
114
+ end
115
+ end
116
+
117
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
118
+ def send_follow_request_to(record)
119
+ if myself?(record)
120
+ errors.add(:base, I18n.t('send_follow_request_to.myself', scope: I18N_SCOPE, klass: record.class))
121
+
122
+ return false
123
+ end
124
+
125
+ if blocked_by?(record)
126
+ errors.add(:base, I18n.t('send_follow_request_to.blocked_by', scope: I18N_SCOPE, klass: record.class.name))
127
+
128
+ return false
129
+ end
130
+
131
+ if following?(record)
132
+ errors.add(:base,
133
+ I18n.t('send_follow_request_to.following', scope: I18N_SCOPE,
134
+ klass: pluralize(record.class.name)))
135
+
136
+ return false
137
+ end
138
+
139
+ if sent_follow_request_to?(record)
140
+ errors.add(:base,
141
+ I18n.t('send_follow_request_to.already_sent', scope: I18N_SCOPE, klass: record.class.name))
142
+
143
+ return false
144
+ end
145
+
146
+ if blocked?(record)
147
+ errors.add(:base, I18n.t('send_follow_request_to.blocked', scope: I18N_SCOPE, klass: record.class.name))
148
+
149
+ return false
150
+ end
151
+
152
+ relation = pending_requests.new(followable: record,
153
+ status: Followability::Relationship.statuses[:requested])
154
+
155
+ if relation.save
156
+ run_callback(self, affected: record, callback: :follow_request_sent_to_someone)
157
+ run_callback(record, affected: self, callback: :follow_request_sent_to_me)
158
+
159
+ true
160
+ else
161
+ errors.add(:base, relation.errors.full_messages.to_sentence)
162
+
163
+ false
164
+ end
165
+ end
166
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
167
+
168
+ def following?(record)
169
+ following.exists?(id: record.id)
170
+ end
171
+
172
+ def mutual_following_with?(record)
173
+ following.exists?(id: record.id) && followers.exists?(id: record.id)
174
+ end
175
+
176
+ def sent_follow_request_to?(record)
177
+ record.follow_requests.exists?(followerable_id: id, followerable_type: self.class.name)
178
+ end
179
+ end
180
+ end
181
+ end
182
+ end
183
+ # rubocop:enable Metrics/ModuleLength
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Followability
4
+ module Followable
5
+ module Associations
6
+ def follow_requests
7
+ followable_relationships.requested
8
+ end
9
+
10
+ def pending_requests
11
+ followerable_relationships.requested
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Followability
4
+ module Followable
5
+ module Callbacks
6
+ METHOD_NAMES = %i[
7
+ follow_request_sent_to_someone
8
+ follow_request_sent_to_me
9
+ follow_request_accepted_by_me
10
+ follow_request_accepted_by_someone
11
+ follow_request_declined_by_me
12
+ follow_request_declined_by_someone
13
+ follow_request_removed_by_me
14
+ follow_request_removed_by_someone
15
+ followable_blocked_by_me
16
+ followable_blocked_by_someone
17
+ followable_unblocked_by_me
18
+ followable_unblocked_by_someone
19
+ unfollow_by_me
20
+ unfollow_by_someone
21
+ followability_triggered
22
+ ].freeze
23
+
24
+ def run_callback(record, callback:, affected:)
25
+ raise ArgumentError if METHOD_NAMES.exclude?(callback) || callback.eql?(:followability_triggered)
26
+
27
+ [callback, :followability_triggered].each do |cb_name|
28
+ record.send(cb_name, affected, cb_name) if record.respond_to?(cb_name)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Followability
4
+ module Followable
5
+ def followability?
6
+ false
7
+ end
8
+
9
+ # rubocop:disable Metrics/MethodLength
10
+ def followability
11
+ class_eval do
12
+ def self.followability?
13
+ true
14
+ end
15
+
16
+ has_many :followerable_relationships,
17
+ as: :followerable,
18
+ class_name: 'Followability::Relationship',
19
+ dependent: :destroy
20
+
21
+ has_many :followable_relationships,
22
+ as: :followable,
23
+ class_name: 'Followability::Relationship',
24
+ dependent: :destroy
25
+
26
+ has_many :followers,
27
+ -> { Followability::Relationship.following },
28
+ through: :followable_relationships,
29
+ source: :followerable,
30
+ class_name: name,
31
+ source_type: name
32
+
33
+ has_many :following,
34
+ -> { Followability::Relationship.following },
35
+ through: :followerable_relationships,
36
+ source: :followable,
37
+ class_name: name,
38
+ source_type: name
39
+
40
+ has_many :blocks,
41
+ -> { Followability::Relationship.blocked },
42
+ through: :followerable_relationships,
43
+ source: :followable,
44
+ class_name: name,
45
+ source_type: name
46
+ end
47
+
48
+ include Followability::Followable::Associations
49
+ include Followability::Followable::Callbacks
50
+ include Followability::Followable::Actions::Common
51
+ include Followability::Followable::Actions::Follow
52
+ include Followability::Followable::Actions::Block
53
+ end
54
+ # rubocop:enable Metrics/MethodLength
55
+ end
56
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+
5
+ module Followability
6
+ module Generators
7
+ class InstallGenerator < Rails::Generators::Base
8
+ source_root File.expand_path('templates', __dir__)
9
+
10
+ def create_migration
11
+ invoke 'migration', migration_args
12
+ copy_file locale_source, locale_destination
13
+ end
14
+
15
+ def fields_command
16
+ %w[
17
+ followerable:belongs_to{polymorphic}
18
+ followable:belongs_to{polymorphic}
19
+ status:integer
20
+ ]
21
+ end
22
+
23
+ def locale_source
24
+ File.expand_path('../../../config/locales/en.yml', __dir__)
25
+ end
26
+
27
+ def locale_destination
28
+ 'config/locales/followability.en.yml'
29
+ end
30
+
31
+ def migration_name
32
+ 'CreateFollowabilityRelationships'
33
+ end
34
+
35
+ def migration_args
36
+ [migration_name].concat(fields_command)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Followability
4
+ class Relationship < ActiveRecord::Base
5
+ STATUSES = %i[requested blocked following].freeze
6
+
7
+ enum status: STATUSES
8
+
9
+ validates :status, presence: true
10
+
11
+ belongs_to :followerable, polymorphic: true, optional: false
12
+ belongs_to :followable, polymorphic: true, optional: false
13
+
14
+ def self.table_name_prefix
15
+ 'followability_'
16
+ end
17
+ end
18
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Followability
4
- VERSION = '0.1.0'
4
+ VERSION = '1.1.0'
5
5
  end
data/lib/followability.rb CHANGED
@@ -1,8 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'followability/version'
4
+ require 'active_support'
5
+ require 'active_record'
6
+ require 'active_model'
4
7
 
5
8
  module Followability
6
- class Error < StandardError; end
7
- # Your code goes here...
9
+ extend ActiveSupport::Autoload
10
+
11
+ require_relative 'followability/followable/associations'
12
+ require_relative 'followability/followable/callbacks'
13
+ require_relative 'followability/followable/actions/common'
14
+ require_relative 'followability/followable/actions/follow'
15
+ require_relative 'followability/followable/actions/block'
16
+ require_relative 'followability/followable'
17
+ require_relative 'followability/relationship'
18
+ require_relative 'followability/generators/install_generator'
19
+ end
20
+
21
+ ActiveSupport.on_load(:active_record) do
22
+ extend Followability::Followable
8
23
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: followability
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - nejdetkadir
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-10-14 00:00:00.000000000 Z
11
+ date: 2022-10-17 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Implements the social network followable functionality for your Active
14
14
  Record models.
@@ -27,8 +27,17 @@ files:
27
27
  - LICENSE
28
28
  - README.md
29
29
  - Rakefile
30
+ - config/locales/en.yml
30
31
  - followability.gemspec
31
32
  - lib/followability.rb
33
+ - lib/followability/followable.rb
34
+ - lib/followability/followable/actions/block.rb
35
+ - lib/followability/followable/actions/common.rb
36
+ - lib/followability/followable/actions/follow.rb
37
+ - lib/followability/followable/associations.rb
38
+ - lib/followability/followable/callbacks.rb
39
+ - lib/followability/generators/install_generator.rb
40
+ - lib/followability/relationship.rb
32
41
  - lib/followability/version.rb
33
42
  - sig/followability.rbs
34
43
  homepage: https://github.com/nejdetkadir/followability