rails_interactable 1.0.1
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +92 -0
- data/Rakefile +6 -0
- data/app/assets/stylesheets/rails_interactable/application.css +15 -0
- data/app/controllers/rails_interactable/application_controller.rb +4 -0
- data/app/helpers/rails_interactable/application_helper.rb +4 -0
- data/app/jobs/rails_interactable/application_job.rb +4 -0
- data/app/mailers/rails_interactable/application_mailer.rb +6 -0
- data/app/models/rails_interactable/application_record.rb +5 -0
- data/app/models/rails_interactable/interaction.rb +15 -0
- data/app/views/layouts/rails_interactable/application.html.erb +17 -0
- data/config/routes.rb +2 -0
- data/lib/generators/rails_interactable/install_generator.rb +32 -0
- data/lib/generators/rails_interactable/templates/create_interactions.rb.tt +15 -0
- data/lib/generators/rails_interactable/templates/initializer.rb +8 -0
- data/lib/rails_interactable/engine.rb +33 -0
- data/lib/rails_interactable/version.rb +3 -0
- data/lib/rails_interactable.rb +279 -0
- data/lib/tasks/rails_interactable_tasks.rake +4 -0
- metadata +80 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: a5ec34551df901686b4d9b3ce19380f91e8eda188a0c1f07d178a086bf2e26a7
|
|
4
|
+
data.tar.gz: d01ef574f2271418dece99138aa68d293a0cbdfc9fcfc99289ee7d14ef09a7c9
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 791979c3e82e8756551e4bb90cd8580def1fe4ac1948d69db7817dbfa3f594989e1995f37d1d788261d0ad857639029f940434df2fcce951a0cc39797a3ff23d
|
|
7
|
+
data.tar.gz: 50f22c6c4ac2680f886e910c58766242b01c381edec4dedf36516c2a6dcff475da4b56b08b6a527114bed90c6693ec10faa9e79bb2f64766ddbac826f83786c3
|
data/MIT-LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright aric.zheng
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# RailsInteractable
|
|
2
|
+
> Add like/favorite interactions to Rails models.
|
|
3
|
+
|
|
4
|
+
## Installation
|
|
5
|
+
|
|
6
|
+
Add this line to your application's Gemfile:
|
|
7
|
+
|
|
8
|
+
```ruby
|
|
9
|
+
gem "rails_interactable"
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
And then execute:
|
|
13
|
+
```bash
|
|
14
|
+
$ bundle install
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Or install it yourself as:
|
|
18
|
+
```bash
|
|
19
|
+
$ gem install rails_interactable
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
1. **Generate Migration and Initializer**: Run the generator to create the necessary database migration and configuration initializer.
|
|
25
|
+
```sh
|
|
26
|
+
rails generate rails_interactable:install
|
|
27
|
+
```
|
|
28
|
+
This command creates:
|
|
29
|
+
* A migration file (`db/migrate/xxx_create_interactions.rb`) for the `interactions` table.
|
|
30
|
+
* An initializer file (`config/initializers/rails_interactable.rb`) to configure interaction types.
|
|
31
|
+
|
|
32
|
+
2. **Run the Migration**: Execute the generated migration to create the `interactions` table in your database.
|
|
33
|
+
```sh
|
|
34
|
+
rails db:migrate
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
3. **Configure Interaction Types**: Edit `config/initializers/rails_interactable.rb` to define the types of interactions your application supports. You can also define aliases for convenience.
|
|
38
|
+
```ruby
|
|
39
|
+
# config/initializers/rails_interactable.rb
|
|
40
|
+
RailsInteractable.interaction_types = {
|
|
41
|
+
like: { alias: :liked_by? },
|
|
42
|
+
favorite: { alias: :favorited_by? }
|
|
43
|
+
# Add more types as needed, e.g.:
|
|
44
|
+
# bookmark: { alias: :bookmarked_by? }
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
4. **Make Models Interactable (as Target)**: Add `acts_as_interactable` to the models that can receive interactions (e.g., `Post`, `Video`).
|
|
49
|
+
```ruby
|
|
50
|
+
# app/models/post.rb
|
|
51
|
+
class Post < ApplicationRecord
|
|
52
|
+
belongs_to :user
|
|
53
|
+
acts_as_interactable # Generates methods like post.likes, post.add_like(user), etc.
|
|
54
|
+
end
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
5. **Make Models an Operator**: Add `acts_as_operator` to the models that perform interactions (e.g., `User`).
|
|
58
|
+
```ruby
|
|
59
|
+
# app/models/user.rb
|
|
60
|
+
class User < ApplicationRecord
|
|
61
|
+
has_many :posts, dependent: :destroy
|
|
62
|
+
acts_as_operator # Generates methods like user.liked, user.liked_of(Post), user.liked?(post), etc.
|
|
63
|
+
end
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## API
|
|
67
|
+
|
|
68
|
+
### On Interactable Models (e.g., `Post`)
|
|
69
|
+
|
|
70
|
+
* **Check Interaction**: `post.interacted_by_like?(user)` / `post.liked_by?(user)` (if alias configured)
|
|
71
|
+
* **Get Interactors**: `post.likes` (list of users who liked), `post.like_ids` (list of user IDs)
|
|
72
|
+
* **Get Count**: `post.like_count`
|
|
73
|
+
* **Add Interaction**: `post.add_like(user)`
|
|
74
|
+
* **Remove Interaction**: `post.remove_like(user)`
|
|
75
|
+
* **Toggle Interaction**: `post.toggle_like(user)`
|
|
76
|
+
* **Generic Check**: `post.interacted_by?(user, 'like')`
|
|
77
|
+
* **Generic Add/Remove/Toggle**: `post.add_interaction(user, 'like')`, `post.remove_interaction(user, 'like')`, `post.toggle_interaction(user, 'like')`
|
|
78
|
+
|
|
79
|
+
### On Operator Models (e.g., `User`)
|
|
80
|
+
|
|
81
|
+
* **Check Interaction**: `user.liked?(post)` / `user.favorited?(post)`
|
|
82
|
+
* **Get Interacted Targets**: `user.liked` (all liked items), `user.favorited` (all favorited items)
|
|
83
|
+
* **Get Interacted Target IDs**: `user.liked_ids` (IDs of all liked items), `user.favorited_ids` (IDs of all favorited items)
|
|
84
|
+
* **Get Interacted Targets of Specific Type and Model**: `user.liked_of(Post)` (all Posts liked by user), `user.favorited_of(Post)` (all Posts favorited by user)
|
|
85
|
+
* **Get Interacted Target IDs of Specific Type and Model**: `user.liked_ids_of(Post)` (IDs of Posts liked by user), `user.favorited_ids_of(Post)` (IDs of Posts favorited by user)
|
|
86
|
+
* **Get Interaction Records**: `user.like_interactions`, `user.favorite_interactions`
|
|
87
|
+
|
|
88
|
+
## Resources
|
|
89
|
+
- https://chat.qwen.ai/c/4d8b4f97-785b-4aa0-87db-29e7671aa3c5
|
|
90
|
+
|
|
91
|
+
## License
|
|
92
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
|
3
|
+
* listed below.
|
|
4
|
+
*
|
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
|
6
|
+
* or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
|
|
7
|
+
*
|
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
|
9
|
+
* compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
|
|
10
|
+
* files in this directory. Styles in this file should be added after the last require_* statement.
|
|
11
|
+
* It is generally better to create a new file per style scope.
|
|
12
|
+
*
|
|
13
|
+
*= require_tree .
|
|
14
|
+
*= require_self
|
|
15
|
+
*/
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# app/models/rails_interactable/interaction.rb
|
|
2
|
+
module RailsInteractable
|
|
3
|
+
class Interaction < ApplicationRecord
|
|
4
|
+
self.table_name = 'rails_interactions'
|
|
5
|
+
|
|
6
|
+
belongs_to :operator, polymorphic: true
|
|
7
|
+
belongs_to :target, polymorphic: true
|
|
8
|
+
|
|
9
|
+
validates :interaction_type, presence: true
|
|
10
|
+
validates :operator, presence: true
|
|
11
|
+
validates :target, presence: true
|
|
12
|
+
validates :operator_type, :operator_id, :target_type, :target_id, :interaction_type, presence: true
|
|
13
|
+
validates :operator_id, uniqueness: { scope: [:operator_type, :target_type, :target_id, :interaction_type] }
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title>Rails interactable</title>
|
|
5
|
+
<%= csrf_meta_tags %>
|
|
6
|
+
<%= csp_meta_tag %>
|
|
7
|
+
|
|
8
|
+
<%= yield :head %>
|
|
9
|
+
|
|
10
|
+
<%= stylesheet_link_tag "rails_interactable/application", media: "all" %>
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
|
|
14
|
+
<%= yield %>
|
|
15
|
+
|
|
16
|
+
</body>
|
|
17
|
+
</html>
|
data/config/routes.rb
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# lib/generators/rails_interactable/install_generator.rb
|
|
2
|
+
require 'rails/generators'
|
|
3
|
+
require 'rails/generators/migration'
|
|
4
|
+
|
|
5
|
+
module RailsInteractable
|
|
6
|
+
class InstallGenerator < Rails::Generators::Base
|
|
7
|
+
include Rails::Generators::Migration
|
|
8
|
+
|
|
9
|
+
source_root File.join(File.dirname(__FILE__), 'templates')
|
|
10
|
+
|
|
11
|
+
def copy_initializer
|
|
12
|
+
template 'initializer.rb', 'config/initializers/rails_interactable.rb'
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# lib/generators/rails_interactable/install_generator.rb
|
|
16
|
+
def copy_migration
|
|
17
|
+
migration_template 'create_interactions.rb.tt', 'db/migrate/create_interactions.rb'
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def migration_timestamp
|
|
23
|
+
Time.current.strftime("%Y%m%d%H%M%S")
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# ActiveRecord::Generators::Base 会自动提供这个方法,但 Base 需要自己实现
|
|
27
|
+
def self.next_migration_number(dirname)
|
|
28
|
+
next_migration_number = current_migration_number(dirname) + 1
|
|
29
|
+
ActiveRecord::Migration.next_migration_number(next_migration_number)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# lib/generators/rails_interactable/templates/create_interactions.rb.tt
|
|
2
|
+
class CreateInteractions < ActiveRecord::Migration[<%= ActiveRecord::VERSION::MAJOR %>.<%= ActiveRecord::VERSION::MINOR %>]
|
|
3
|
+
def change
|
|
4
|
+
create_table :rails_interactions do |t|
|
|
5
|
+
# 使用 interaction_type 而不是 type
|
|
6
|
+
t.string :interaction_type, null: false
|
|
7
|
+
t.references :operator, polymorphic: true, null: false, index: { name: 'index_interactions_on_op_type_and_op_id' }
|
|
8
|
+
t.references :target, polymorphic: true, null: false, index: { name: 'index_interactions_on_tgt_type_and_tgt_id' }
|
|
9
|
+
|
|
10
|
+
t.timestamps
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
add_index :interactions, [:operator_type, :operator_id, :interaction_type, :target_type, :target_id], unique: true, name: 'index_unique_interaction'
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# lib/rails_interactable/engine.rb
|
|
2
|
+
require 'rails'
|
|
3
|
+
require_relative '../rails_interactable'
|
|
4
|
+
|
|
5
|
+
module RailsInteractable
|
|
6
|
+
class Engine < ::Rails::Engine
|
|
7
|
+
isolate_namespace RailsInteractable
|
|
8
|
+
|
|
9
|
+
# 加载插件内部的迁移
|
|
10
|
+
initializer "rails_interactable.load_migrations" do
|
|
11
|
+
config.paths["db/migrate"].expanded.each do |expanded_path|
|
|
12
|
+
Rails.application.config.paths["db/migrate"] << expanded_path
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# 配置初始化
|
|
17
|
+
# 注意:如果配置在插件内部初始化,可能在 dummy app 中被覆盖
|
|
18
|
+
# 更好的方式是让使用者在 config/initializers 中配置
|
|
19
|
+
# initializer "rails_interactable.configure" do |app|
|
|
20
|
+
# RailsInteractable.interaction_types = {
|
|
21
|
+
# like: { alias: :liked_by? },
|
|
22
|
+
# favorite: { alias: :favorited_by? }
|
|
23
|
+
# }
|
|
24
|
+
# end
|
|
25
|
+
|
|
26
|
+
# 混入模块
|
|
27
|
+
initializer "rails_interactable.active_record" do
|
|
28
|
+
ActiveSupport.on_load :active_record do
|
|
29
|
+
include RailsInteractable
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
# lib/rails_interactable.rb
|
|
2
|
+
require "rails_interactable/engine"
|
|
3
|
+
|
|
4
|
+
module RailsInteractable
|
|
5
|
+
# 存储全局配置
|
|
6
|
+
mattr_accessor :interaction_types, default: {}
|
|
7
|
+
|
|
8
|
+
extend ActiveSupport::Concern
|
|
9
|
+
|
|
10
|
+
class_methods do
|
|
11
|
+
# 模型只需声明自己是可互动的 (作为 target)
|
|
12
|
+
def acts_as_interactable
|
|
13
|
+
include InstanceMethods
|
|
14
|
+
# 在模型类被加载时,根据全局配置动态定义方法
|
|
15
|
+
RailsInteractable.interaction_types.each do |type, config|
|
|
16
|
+
define_interaction_methods_for_type(type, config)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# 模型声明自己是互动的操作者 (operator)
|
|
21
|
+
def acts_as_operator
|
|
22
|
+
include OperatorInstanceMethods
|
|
23
|
+
# 在模型类被加载时,根据全局配置动态定义方法 (operator 角度)
|
|
24
|
+
RailsInteractable.interaction_types.each do |type, config|
|
|
25
|
+
define_operator_interaction_methods_for_type(type, config)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
# 动态定义与特定互动类型相关的方法 (target 角度)
|
|
32
|
+
def define_interaction_methods_for_type(type, config)
|
|
33
|
+
type_sym = type.to_sym
|
|
34
|
+
type_str = type.to_s
|
|
35
|
+
|
|
36
|
+
# 1. 检查互动状态: interacted_by_TYPE?(operator)
|
|
37
|
+
define_method "interacted_by_#{type_sym}?" do |operator|
|
|
38
|
+
RailsInteractable::Interaction.where(
|
|
39
|
+
target: self,
|
|
40
|
+
operator: operator,
|
|
41
|
+
interaction_type: type_str
|
|
42
|
+
).exists?
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# 2. 获取互动者列表: TYPEers
|
|
46
|
+
define_method "#{type_sym}s" do
|
|
47
|
+
# 重用 interactors 方法的逻辑
|
|
48
|
+
interactors(type_str)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# 2.5. 获取互动者ID列表: TYPE_ids
|
|
52
|
+
define_method "#{type_sym}_ids" do
|
|
53
|
+
# 重用 interactors_ids 方法的逻辑
|
|
54
|
+
interactors_ids(type_str)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# 3. 获取互动计数: TYPE_count
|
|
58
|
+
define_method "#{type_sym}_count" do
|
|
59
|
+
RailsInteractable::Interaction.where(target: self, interaction_type: type_str).count
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# 4. 添加互动: add_TYPE(operator)
|
|
63
|
+
define_method "add_#{type_sym}" do |operator|
|
|
64
|
+
unless RailsInteractable::Interaction.where(
|
|
65
|
+
target: self,
|
|
66
|
+
operator: operator,
|
|
67
|
+
interaction_type: type_str
|
|
68
|
+
).exists?
|
|
69
|
+
RailsInteractable::Interaction.create!(
|
|
70
|
+
target: self,
|
|
71
|
+
operator: operator,
|
|
72
|
+
interaction_type: type_str
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# 5. 移除互动: remove_TYPE(operator)
|
|
78
|
+
define_method "remove_#{type_sym}" do |operator|
|
|
79
|
+
interaction = RailsInteractable::Interaction.where(
|
|
80
|
+
target: self,
|
|
81
|
+
operator: operator,
|
|
82
|
+
interaction_type: type_str
|
|
83
|
+
).first
|
|
84
|
+
interaction&.destroy
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# 6. 切换互动: toggle_TYPE(operator)
|
|
88
|
+
define_method "toggle_#{type_sym}" do |operator|
|
|
89
|
+
if send("interacted_by_#{type_sym}?", operator)
|
|
90
|
+
send("remove_#{type_sym}", operator)
|
|
91
|
+
else
|
|
92
|
+
send("add_#{type_sym}", operator)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# 7. 根据配置添加别名等
|
|
97
|
+
if config && config[:alias]
|
|
98
|
+
alias_method config[:alias], "interacted_by_#{type_sym}?"
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# 动态定义与特定互动类型相关的方法 (operator 角度)
|
|
103
|
+
def define_operator_interaction_methods_for_type(type, config)
|
|
104
|
+
type_sym = type.to_sym
|
|
105
|
+
type_str = type.to_s
|
|
106
|
+
|
|
107
|
+
# 1. 获取操作者发起的特定类型互动的目标对象: TYPEd (e.g., user.liked, user.favorited)
|
|
108
|
+
define_method "#{type_sym}d" do
|
|
109
|
+
targets_of_type(type_str)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# 1.5. 获取操作者发起的特定类型、特定目标模型的互动对象: TYPEd_of(TargetModel) (e.g., user.liked_of(Post), user.favorited_of(Post))
|
|
113
|
+
define_method "#{type_sym}d_of" do |target_class|
|
|
114
|
+
targets_of_type_and_class(type_str, target_class)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# 2. 获取操作者发起的特定类型互动的目标对象ID: TYPEd_ids (e.g., user.liked_ids, user.favorited_ids)
|
|
118
|
+
define_method "#{type_sym}_ids" do
|
|
119
|
+
target_ids_of_type(type_str)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# 2.5. 获取操作者发起的特定类型、特定目标模型的互动对象ID: TYPEd_ids_of(TargetModel) (e.g., user.liked_ids_of(Post), user.favorited_ids_of(Post))
|
|
123
|
+
define_method "#{type_sym}_ids_of" do |target_class|
|
|
124
|
+
target_ids_of_type_and_class(type_str, target_class)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# 3. 检查操作者是否对某个目标执行了特定类型互动: TYPEd?(target) (e.g., user.liked?(post), user.favorited?(post))
|
|
128
|
+
define_method "#{type_sym}d?" do |target|
|
|
129
|
+
interacted_with?(target, type_str)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# 4. 获取操作者发起的特定类型互动记录: TYPE_interactions (e.g., user.like_interactions, user.favorite_interactions)
|
|
133
|
+
define_method "#{type_sym}_interactions" do
|
|
134
|
+
interactions_of_type(type_str)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
module InstanceMethods
|
|
140
|
+
# 通用方法 - 使用 interaction_type
|
|
141
|
+
def interacted_by?(operator, type)
|
|
142
|
+
RailsInteractable::Interaction.where(
|
|
143
|
+
target: self,
|
|
144
|
+
operator: operator,
|
|
145
|
+
interaction_type: type.to_s
|
|
146
|
+
).exists?
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# 修改 interactors 方法,移除 joins,避免多态关联加载错误
|
|
150
|
+
def interactors(type)
|
|
151
|
+
# 首先获取所有相关交互的 operator_type 和 operator_id
|
|
152
|
+
operator_data = RailsInteractable::Interaction
|
|
153
|
+
.where(target: self, interaction_type: type.to_s)
|
|
154
|
+
.pluck(:operator_type, :operator_id)
|
|
155
|
+
|
|
156
|
+
# 按 operator_type 分组
|
|
157
|
+
operators_by_type = operator_data.group_by(&:first).transform_values { |group| group.map(&:last).uniq }
|
|
158
|
+
|
|
159
|
+
# 为每种类型批量查询对应的模型实例
|
|
160
|
+
results = []
|
|
161
|
+
operators_by_type.each do |operator_type, operator_ids|
|
|
162
|
+
operator_class = operator_type.constantize
|
|
163
|
+
instances = operator_class.where(id: operator_ids)
|
|
164
|
+
results.concat(instances)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
results
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# 新增:获取互动者ID列表
|
|
171
|
+
def interactors_ids(type)
|
|
172
|
+
RailsInteractable::Interaction
|
|
173
|
+
.where(target: self, interaction_type: type.to_s)
|
|
174
|
+
.pluck(:operator_id)
|
|
175
|
+
.uniq
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def interaction_count(type)
|
|
179
|
+
RailsInteractable::Interaction.where(target: self, interaction_type: type.to_s).count
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# 通用方法
|
|
183
|
+
def add_interaction(operator, type)
|
|
184
|
+
unless RailsInteractable::Interaction.where(
|
|
185
|
+
target: self,
|
|
186
|
+
operator: operator,
|
|
187
|
+
interaction_type: type.to_s
|
|
188
|
+
).exists?
|
|
189
|
+
RailsInteractable::Interaction.create!(
|
|
190
|
+
target: self,
|
|
191
|
+
operator: operator,
|
|
192
|
+
interaction_type: type.to_s
|
|
193
|
+
)
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def remove_interaction(operator, type)
|
|
198
|
+
interaction = RailsInteractable::Interaction.where(
|
|
199
|
+
target: self,
|
|
200
|
+
operator: operator,
|
|
201
|
+
interaction_type: type.to_s
|
|
202
|
+
).first
|
|
203
|
+
interaction&.destroy
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def toggle_interaction(operator, type)
|
|
207
|
+
if interacted_by?(operator, type)
|
|
208
|
+
remove_interaction(operator, type)
|
|
209
|
+
else
|
|
210
|
+
add_interaction(operator, type)
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# 新增:Operator 相关的实例方法
|
|
216
|
+
module OperatorInstanceMethods
|
|
217
|
+
# 通用方法:获取操作者发起的特定类型互动
|
|
218
|
+
def interactions_of_type(type)
|
|
219
|
+
RailsInteractable::Interaction.where(
|
|
220
|
+
operator: self,
|
|
221
|
+
interaction_type: type.to_s
|
|
222
|
+
)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# 通用方法:获取操作者发起的特定类型互动的目标对象
|
|
226
|
+
def targets_of_type(type)
|
|
227
|
+
interaction_targets = RailsInteractable::Interaction
|
|
228
|
+
.where(operator: self, interaction_type: type.to_s)
|
|
229
|
+
.pluck(:target_type, :target_id)
|
|
230
|
+
|
|
231
|
+
targets_by_type = interaction_targets.group_by(&:first).transform_values { |group| group.map(&:last).uniq }
|
|
232
|
+
|
|
233
|
+
results = []
|
|
234
|
+
targets_by_type.each do |target_type, target_ids|
|
|
235
|
+
target_class = target_type.constantize
|
|
236
|
+
instances = target_class.where(id: target_ids)
|
|
237
|
+
results.concat(instances)
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
results
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# 新增:获取操作者发起的特定类型、特定目标模型的互动对象
|
|
244
|
+
def targets_of_type_and_class(type, target_class)
|
|
245
|
+
target_class_name = target_class.name
|
|
246
|
+
interaction_target_ids = RailsInteractable::Interaction
|
|
247
|
+
.where(operator: self, interaction_type: type.to_s, target_type: target_class_name)
|
|
248
|
+
.pluck(:target_id)
|
|
249
|
+
|
|
250
|
+
target_class.where(id: interaction_target_ids)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# 通用方法:获取操作者发起的特定类型互动的目标对象ID
|
|
254
|
+
def target_ids_of_type(type)
|
|
255
|
+
RailsInteractable::Interaction
|
|
256
|
+
.where(operator: self, interaction_type: type.to_s)
|
|
257
|
+
.pluck(:target_id)
|
|
258
|
+
.uniq
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# 新增:获取操作者发起的特定类型、特定目标模型的互动对象ID
|
|
262
|
+
def target_ids_of_type_and_class(type, target_class)
|
|
263
|
+
target_class_name = target_class.name
|
|
264
|
+
RailsInteractable::Interaction
|
|
265
|
+
.where(operator: self, interaction_type: type.to_s, target_type: target_class_name)
|
|
266
|
+
.pluck(:target_id)
|
|
267
|
+
.uniq
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# 通用方法:检查操作者是否对某个目标执行了特定类型互动
|
|
271
|
+
def interacted_with?(target, type)
|
|
272
|
+
RailsInteractable::Interaction.where(
|
|
273
|
+
operator: self,
|
|
274
|
+
target: target,
|
|
275
|
+
interaction_type: type.to_s
|
|
276
|
+
).exists?
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: rails_interactable
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- aric.zheng
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2025-11-10 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: rails
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: 8.1.1
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: 8.1.1
|
|
27
|
+
description: A Rails gem for adding like, favorite, and other interaction features
|
|
28
|
+
to models via dynamic method generation.
|
|
29
|
+
email:
|
|
30
|
+
- 1290657123@qq.com
|
|
31
|
+
executables: []
|
|
32
|
+
extensions: []
|
|
33
|
+
extra_rdoc_files: []
|
|
34
|
+
files:
|
|
35
|
+
- MIT-LICENSE
|
|
36
|
+
- README.md
|
|
37
|
+
- Rakefile
|
|
38
|
+
- app/assets/stylesheets/rails_interactable/application.css
|
|
39
|
+
- app/controllers/rails_interactable/application_controller.rb
|
|
40
|
+
- app/helpers/rails_interactable/application_helper.rb
|
|
41
|
+
- app/jobs/rails_interactable/application_job.rb
|
|
42
|
+
- app/mailers/rails_interactable/application_mailer.rb
|
|
43
|
+
- app/models/rails_interactable/application_record.rb
|
|
44
|
+
- app/models/rails_interactable/interaction.rb
|
|
45
|
+
- app/views/layouts/rails_interactable/application.html.erb
|
|
46
|
+
- config/routes.rb
|
|
47
|
+
- lib/generators/rails_interactable/install_generator.rb
|
|
48
|
+
- lib/generators/rails_interactable/templates/create_interactions.rb.tt
|
|
49
|
+
- lib/generators/rails_interactable/templates/initializer.rb
|
|
50
|
+
- lib/rails_interactable.rb
|
|
51
|
+
- lib/rails_interactable/engine.rb
|
|
52
|
+
- lib/rails_interactable/version.rb
|
|
53
|
+
- lib/tasks/rails_interactable_tasks.rake
|
|
54
|
+
homepage: https://github.com/afeiship/rails_interactable
|
|
55
|
+
licenses:
|
|
56
|
+
- MIT
|
|
57
|
+
metadata:
|
|
58
|
+
homepage_uri: https://github.com/afeiship/rails_interactable
|
|
59
|
+
source_code_uri: https://github.com/afeiship/rails_interactable
|
|
60
|
+
changelog_uri: https://github.com/afeiship/rails_interactable/blob/main/CHANGELOG.md
|
|
61
|
+
post_install_message:
|
|
62
|
+
rdoc_options: []
|
|
63
|
+
require_paths:
|
|
64
|
+
- lib
|
|
65
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
66
|
+
requirements:
|
|
67
|
+
- - ">="
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
69
|
+
version: '0'
|
|
70
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '0'
|
|
75
|
+
requirements: []
|
|
76
|
+
rubygems_version: 3.5.22
|
|
77
|
+
signing_key:
|
|
78
|
+
specification_version: 4
|
|
79
|
+
summary: Add like/favorite interactions to Rails models.
|
|
80
|
+
test_files: []
|