followability 0.1.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 96a742ecd47a5d0cc1f9c5247d991f5da15540fe32f4ed1f5cb340ff42f75560
4
- data.tar.gz: d3f37786e80e7d1adbc6e74de84b43f9c2e923208cc3673d3557a2b3baa73f8c
3
+ metadata.gz: 74981f3a807c733f78c36f78ebec08e18f9469415ca5fe500ba902a244f2490a
4
+ data.tar.gz: 49109e4a45692f73f206b3847610d0c725a8837b7a561d5f99b5641e6e53e89c
5
5
  SHA512:
6
- metadata.gz: 990e5646cb61a4300be524bb48e875012daa5faff1362ebf9ba6ed2547a0b66cb323b27e4e882e30a486986750ae201ec6ad858dcf8f35c34a390fb2f07400c6
7
- data.tar.gz: 1901bab6ec5670d0337357789fb2e8d82192a469d00e7d5345970e21269435281df397fbd50cf39fdce7b98996eb83e5947c5fe1d5302215b83b1ac3a7f0e4e3
6
+ metadata.gz: cd0716f68c519a24544e47d9d88c21a1eb006485c99b92419cd166dcd847c9876e2b3fa3df968afd061e8ce3a13ff481893e8dd09f331aad47e045a505b5ab4b
7
+ data.tar.gz: 29b0bda4d267f4d8b34a1a5f438e64eb5135e775af9cb283815570e417fd35eb322e7e1b612062b3a2972fc58904b70c41bfe353d9f66cd12758f24277170fe2
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
@@ -6,10 +6,77 @@ PATH
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,228 @@ 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
+ - following?
51
+ - mutual_following_with?
52
+ - sent_follow_request_to?
53
+
54
+ ### Usage
55
+ ```ruby
56
+ @foo = User.first
57
+ @bar = User.last
58
+
59
+ @foo.send_follow_request_to(@bar)
60
+ # => true
61
+
62
+ @foo.sent_follow_request_to?(@bar)
63
+ # => true
64
+
65
+ @bar.decline_follow_request_of(@foo)
66
+ # => true
67
+
68
+ @bar.accept_follow_request_of(@foo)
69
+ # => false
70
+
71
+ @bar.errors.full_messages
72
+ # => [...]
73
+
74
+ @foo.remove_follow_request_for(@bar)
75
+ # => false
76
+
77
+ @foo.errors.full_messages
78
+ # => [...]
79
+
80
+ @foo.mutual_following_with?(@bar)
81
+ # => false
82
+
83
+ @foo.errors.full_messages
84
+ # => [...]
85
+
86
+ @bar.following?(@foo)
87
+ # => false
88
+
89
+ @foo.errors.full_messages
90
+ # => [...]
91
+
92
+ ```
93
+
94
+ ### Blocking actions
95
+ Avaiable methods:
96
+ - block_to
97
+ - unblock_to
98
+ - blocked?
99
+ - blocked_by?
100
+
101
+ ### Usage
102
+ ```ruby
103
+ @foo.block_to(@bar)
104
+ # => true
105
+
106
+ @foo.blocked?(@bar)
107
+ # => true
108
+
109
+ @bar.blocked_by?(@foo)
110
+ # => true
111
+
112
+ @foo.unblock_to(@bar)
113
+ # => true
114
+ ```
115
+
116
+ ### Common
117
+ Avaiable methods:
118
+ - myself?
119
+
120
+ ### Usage
121
+ ```ruby
122
+ class User < ActiveRecord::Base
123
+ followability
124
+
125
+ def follow_request_removed_by_someone(record)
126
+ unless myself?(record)
127
+ # Do something
128
+ end
129
+ end
130
+ end
131
+ ```
132
+
133
+ ### Relations
134
+ Avaiable methods:
135
+ - follow_requests
136
+ - pending_requests
137
+ - followerable_relationships
138
+ - followable_relationships
139
+ - followers
140
+ - following
141
+ - blocks
142
+
143
+ ### Usage
144
+ ```ruby
145
+ @foo.follow_requests
146
+ # => [#<Followability::Relationship ...>]
147
+
148
+ @foo.pending_requests
149
+ # => [#<Followability::Relationship ...>]
150
+
151
+ @foo.followerable_relationships
152
+ # => [#<Followability::Relationship ...>]
153
+
154
+ @foo.followable_relationships
155
+ # => [#<Followability::Relationship ...>]
156
+
157
+ @foo.followers
158
+ # => [#<User ...>]
159
+
160
+ @foo.following
161
+ # => [#<User ...>]
162
+
163
+ @foo.blocks
164
+ # => [#<User ...>]
165
+ ```
166
+
167
+ ### Callback Methods
168
+ Available methods:
169
+ - follow_request_sent_to_me
170
+ - follow_request_sent_to_someone
171
+ - follow_request_accepted_by_me
172
+ - follow_request_accepted_by_someone
173
+ - follow_request_declined_by_me
174
+ - follow_request_declined_by_someone
175
+ - follow_request_removed_by_me
176
+ - follow_request_removed_by_someone
177
+ - followable_blocked_by_me
178
+ - followable_blocked_by_someone
179
+ - followable_unblocked_by_me
180
+ - followable_unblocked_by_someone
181
+ - followability_triggered
182
+
183
+ ### Usage
184
+ ```ruby
185
+ class User < ActiveRecord::Base
186
+ followability
187
+
188
+ def follow_request_sent_to_me(record)
189
+ Notifications::FollowRequestSentToMeJob.perform_later(from_id: record.id)
190
+ end
191
+ def follow_request_sent_to_someone(record); end
192
+ def follow_request_accepted_by_me(record); end
193
+ def follow_request_accepted_by_someone(record); end
194
+ def follow_request_declined_by_me(record); end
195
+ def follow_request_declined_by_someone(record); end
196
+ def follow_request_removed_by_me(record); end
197
+ def follow_request_removed_by_someone(record); end
198
+ def followable_blocked_by_me(record); end
199
+ def followable_blocked_by_someone(record); end
200
+ def followable_unblocked_by_me(record); end
201
+ def followable_unblocked_by_someone(record); end
202
+ def followability_triggered(record, callback_name); end
203
+ end
204
+ ```
205
+
206
+ ## I18n
207
+ ```yml
208
+ ---
209
+ ---
210
+ en:
211
+ followability:
212
+ errors:
213
+ block:
214
+ unblock_to:
215
+ myself: 'You can not run this action for yourself'
216
+ block_to:
217
+ myself: 'You can not run this action for yourself'
218
+ blocked_by: 'You can not block to who blocked to you'
219
+ already_blocked: '%{klass} already blocked'
220
+ not_blocked_for_blocking: 'You can not unblock to %{klass} because was not blocked'
221
+ follow:
222
+ decline_follow_request_of:
223
+ myself: 'You can not run this action for yourself'
224
+ empty_relation: 'You can not decline follow request of %{klass} because was not sent'
225
+ accept_follow_request_of:
226
+ myself: 'You can not run this action for yourself'
227
+ empty_relation: 'You can not accept follow request of %{klass} because was not sent'
228
+ remove_follow_request_for:
229
+ empty_relation: 'You can not remove follow request of %{klass} because was not sent'
230
+ myself: 'You can not run this action for yourself'
231
+ send_follow_request_to:
232
+ myself: 'You can not run this action for yourself'
233
+ blocked_by: 'You can not send follow request to who blocked to you'
234
+ following: 'You are already following to %{klass}'
235
+ already_sent: 'You are already sent follow request'
236
+ blocked: 'You can not send follow request to blocked %{klass}'
237
+ ```
25
238
 
26
239
  ## Development
27
240
 
@@ -0,0 +1,28 @@
1
+ ---
2
+ en:
3
+ followability:
4
+ errors:
5
+ block:
6
+ unblock_to:
7
+ myself: 'You can not run this action for yourself'
8
+ block_to:
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
+ decline_follow_request_of:
15
+ myself: 'You can not run this action for yourself'
16
+ empty_relation: 'You can not decline follow request of %{klass} because was not sent'
17
+ accept_follow_request_of:
18
+ myself: 'You can not run this action for yourself'
19
+ empty_relation: 'You can not accept follow request of %{klass} because was not sent'
20
+ remove_follow_request_for:
21
+ empty_relation: 'You can not remove follow request of %{klass} because was not sent'
22
+ myself: 'You can not run this action for yourself'
23
+ send_follow_request_to:
24
+ myself: 'You can not run this action for yourself'
25
+ blocked_by: 'You can not send follow request to who blocked to you'
26
+ following: 'You are already following to %{klass}'
27
+ already_sent: 'You are already sent follow request'
28
+ 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_to(record)
10
+ if myself?(record)
11
+ errors.add(:base, I18n.t('block_to.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_to.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_to(record)
44
+ if myself?(record)
45
+ errors.add(:base, I18n.t('unblock_to.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,154 @@
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 accept_follow_request_of(record)
37
+ if myself?(record)
38
+ errors.add(:base, I18n.t('accept_follow_request_of.myself', scope: I18N_SCOPE, klass: record.class))
39
+
40
+ return false
41
+ end
42
+
43
+ relation = follow_requests.find_by(followerable_id: record.id, followerable_type: record.class.name)
44
+
45
+ if relation.blank?
46
+ errors.add(:base,
47
+ I18n.t('accept_follow_request_of.empty_relation', scope: I18N_SCOPE, klass: record.class.name))
48
+
49
+ false
50
+ elsif relation.update(status: Followability::Relationship.statuses[:following])
51
+ run_callback(record, affected: record, callback: :follow_request_accepted_by_someone)
52
+ run_callback(self, affected: self, callback: :follow_request_accepted_by_me)
53
+
54
+ true
55
+ else
56
+ errors.add(:base, relation.errors.full_messages.to_sentence)
57
+
58
+ false
59
+ end
60
+ end
61
+
62
+ def remove_follow_request_for(record)
63
+ if myself?(record)
64
+ errors.add(:base, I18n.t('remove_follow_request_for.myself', scope: I18N_SCOPE, klass: record.class))
65
+
66
+ return false
67
+ end
68
+
69
+ relation = pending_requests.find_by(followable_id: record.id, followable_type: record.class.name)
70
+
71
+ if relation.blank?
72
+ errors.add(:base,
73
+ I18n.t('remove_follow_request_for.empty_relation', scope: I18N_SCOPE, klass: record.class.name))
74
+
75
+ false
76
+ elsif relation.destroy
77
+ run_callback(self, affected: record, callback: :follow_request_removed_by_me)
78
+ run_callback(record, affected: self, callback: :follow_request_removed_by_someone)
79
+
80
+ true
81
+ else
82
+ errors.add(:base, relation.errors.full_messages.to_sentence)
83
+
84
+ false
85
+ end
86
+ end
87
+
88
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
89
+ def send_follow_request_to(record)
90
+ if myself?(record)
91
+ errors.add(:base, I18n.t('send_follow_request_to.myself', scope: I18N_SCOPE, klass: record.class))
92
+
93
+ return false
94
+ end
95
+
96
+ if blocked_by?(record)
97
+ errors.add(:base, I18n.t('send_follow_request_to.blocked_by', scope: I18N_SCOPE, klass: record.class.name))
98
+
99
+ return false
100
+ end
101
+
102
+ if following?(record)
103
+ errors.add(:base,
104
+ I18n.t('send_follow_request_to.following', scope: I18N_SCOPE,
105
+ klass: pluralize(record.class.name)))
106
+
107
+ return false
108
+ end
109
+
110
+ if sent_follow_request_to?(record)
111
+ errors.add(:base,
112
+ I18n.t('send_follow_request_to.already_sent', scope: I18N_SCOPE, klass: record.class.name))
113
+
114
+ return false
115
+ end
116
+
117
+ if blocked?(record)
118
+ errors.add(:base, I18n.t('send_follow_request_to.blocked', scope: I18N_SCOPE, klass: record.class.name))
119
+
120
+ return false
121
+ end
122
+
123
+ relation = pending_requests.new(followable: record,
124
+ status: Followability::Relationship.statuses[:requested])
125
+
126
+ if relation.save
127
+ run_callback(self, affected: record, callback: :follow_request_sent_to_someone)
128
+ run_callback(record, affected: self, callback: :follow_request_sent_to_me)
129
+
130
+ true
131
+ else
132
+ errors.add(:base, relation.errors.full_messages.to_sentence)
133
+
134
+ false
135
+ end
136
+ end
137
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
138
+
139
+ def following?(record)
140
+ following.exists?(id: record.id)
141
+ end
142
+
143
+ def mutual_following_with?(record)
144
+ following.exists?(id: record.id) && followers.exists?(id: record.id)
145
+ end
146
+
147
+ def sent_follow_request_to?(record)
148
+ record.follow_requests.exists?(followerable_id: id, followerable_type: self.class.name)
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
154
+ # 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,31 @@
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
+ followability_triggered
20
+ ].freeze
21
+
22
+ def run_callback(record, callback:, affected:)
23
+ raise ArgumentError if METHOD_NAMES.exclude?(callback) || callback.eql?(:followability_triggered)
24
+
25
+ [callback, :followability_triggered].each do |cb_name|
26
+ record.send(cb_name, affected, cb_name) if record.respond_to?(cb_name)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ 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.0.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.0.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-15 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