followability 0.1.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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