yiffspace-user 0.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/CHANGELOG.md +6 -0
- data/LICENSE +20 -0
- data/README.md +28 -0
- data/Rakefile +9 -0
- data/lib/yiffspace/concerns/current_methods.rb +60 -0
- data/lib/yiffspace/concerns/user_class_methods.rb +32 -0
- data/lib/yiffspace/concerns/user_methods.rb +77 -0
- data/lib/yiffspace/concerns/user_name_methods.rb +53 -0
- data/lib/yiffspace/concerns/user_resolvable_methods.rb +38 -0
- data/lib/yiffspace/serializers/user_resolvable_serializer.rb +21 -0
- data/lib/yiffspace/user/attribute.rb +272 -0
- data/lib/yiffspace/user/current.rb +43 -0
- data/lib/yiffspace/user/engine.rb +18 -0
- data/lib/yiffspace/user/like.rb +8 -0
- data/lib/yiffspace/user/resolvable.rb +61 -0
- data/lib/yiffspace/user/to_id.rb +11 -0
- data/lib/yiffspace/user/version.rb +7 -0
- data/lib/yiffspace/user.rb +86 -0
- metadata +121 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: db2366c67818c97d4f8ce3ecb53816f6a84e0fb2a3b9faf952445a385d2f6a51
|
|
4
|
+
data.tar.gz: bff3d69f13b880865ff6f812fff8eb8da65108f6ed3bd9d87cf5d7cb4c570ce2
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: c616f03209b48f1bc85bfa134c2ac969b9eda29c0fee241124dde30757c68521f92e7e201161f2d0c86ed327bfc4508393583e07c2c81b9e986f22c1d0bcc8cc
|
|
7
|
+
data.tar.gz: 503228eec1c7c14fe14be6bb53aaac465b3e0cdb93dfc1fa99f9093e5ef9c2223264273d6eca95f6cc19f6ec9eb2994031e3ec2673a66c459bdd76ba19321a13
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
## 0.0.1
|
|
2
|
+
|
|
3
|
+
- Initial release, extracted from the `yiffspace` gem's User/Current/Resolvable
|
|
4
|
+
concerns and utils. `YiffSpace::Utils::Current`/`UserLike`/`UserResolvable`/
|
|
5
|
+
`UserAttribute`/`UserToId` are now `YiffSpace::User::Current`/`Like`/
|
|
6
|
+
`Resolvable`/`Attribute`/`ToId`.
|
data/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright Donovan_DMC
|
|
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,28 @@
|
|
|
1
|
+
# YiffSpace::User
|
|
2
|
+
|
|
3
|
+
The User/Current/Resolvable concerns and utils - `YiffSpace::User::Current`,
|
|
4
|
+
`YiffSpace::User::Resolvable`, `YiffSpace::User::Like`, `YiffSpace::User::Attribute`,
|
|
5
|
+
`YiffSpace::User::ToId`, plus the `Concerns::User*`/`Concerns::Current*` model
|
|
6
|
+
concerns - for https://yiff.space and related projects.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
Add this line to your application's Gemfile:
|
|
11
|
+
|
|
12
|
+
```ruby
|
|
13
|
+
gem "yiffspace-user"
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
And then execute:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
$ bundle install
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Contributing
|
|
23
|
+
|
|
24
|
+
Go away
|
|
25
|
+
|
|
26
|
+
## License
|
|
27
|
+
|
|
28
|
+
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,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require("bundler/gem_tasks")
|
|
4
|
+
|
|
5
|
+
# CI releases from an already-tagged, already-pushed commit checked out at a detached HEAD, so
|
|
6
|
+
# the default release task's git tag/push step has nothing to do and fails trying to push a
|
|
7
|
+
# branch ref that does not exist in that checkout.
|
|
8
|
+
Rake::Task["release"].clear
|
|
9
|
+
task("release" => "release:rubygem_push")
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module YiffSpace
|
|
4
|
+
module Concerns
|
|
5
|
+
module CurrentMethods
|
|
6
|
+
extend(ActiveSupport::Concern)
|
|
7
|
+
|
|
8
|
+
module ClassMethods
|
|
9
|
+
def search_current(params, ...)
|
|
10
|
+
search(params, YiffSpace.config.current_class.user, ...)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def with_current(instance, method, *args, **, &)
|
|
14
|
+
attr = args.grep(Symbol)
|
|
15
|
+
other = args - attr
|
|
16
|
+
hashes = other.grep(Hash)
|
|
17
|
+
params = other.grep(ActionController::Parameters)
|
|
18
|
+
other -= hashes + params
|
|
19
|
+
other.compact_blank!
|
|
20
|
+
options = [attr.index_with(YiffSpace.config.current_class.user), *hashes, *params.map(&:to_hash)].inject(:merge)
|
|
21
|
+
instance.send(method, *other, **options, **, &)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def new_with_current(...)
|
|
25
|
+
with_current(self, :new, ...)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def create_with_current(...)
|
|
29
|
+
with_current(self, :create, ...)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def create_with_current!(...)
|
|
33
|
+
with_current(self, :create!, ...)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def update_with_current(...)
|
|
38
|
+
self.class.with_current(self, :update, ...)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def update_with_current!(...)
|
|
42
|
+
self.class.with_current(self, :update!, ...)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def destroy_with_current(*attrs)
|
|
46
|
+
attrs.each do |attr|
|
|
47
|
+
send("#{attr}=", YiffSpace.config.current_class.user)
|
|
48
|
+
end
|
|
49
|
+
destroy
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def destroy_with_current!(*attrs)
|
|
53
|
+
attrs.each do |attr|
|
|
54
|
+
send("#{attr}=", YiffSpace.config.current_class.user)
|
|
55
|
+
end
|
|
56
|
+
destroy!
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module YiffSpace
|
|
4
|
+
module Concerns
|
|
5
|
+
# methods to include into the user class for use with the like/resolvable classes
|
|
6
|
+
module UserClassMethods
|
|
7
|
+
extend(ActiveSupport::Concern)
|
|
8
|
+
|
|
9
|
+
def resolvable(ip_addr = nil)
|
|
10
|
+
YiffSpace.config.user_resolvable_class.new(self, ip_addr || "127.0.0.1")
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def resolve
|
|
14
|
+
self
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def ==(other)
|
|
18
|
+
return super if other.is_a?(YiffSpace.config.user_class)
|
|
19
|
+
|
|
20
|
+
other.is_a?(YiffSpace.config.user_resolvable_class) && super(other.user)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def ===(other)
|
|
24
|
+
other == YiffSpace.config.user_like_class || super
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def is_a?(other)
|
|
28
|
+
other == YiffSpace.config.user_like_class || super
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module YiffSpace
|
|
4
|
+
module Concerns
|
|
5
|
+
module UserMethods
|
|
6
|
+
extend(ActiveSupport::Concern)
|
|
7
|
+
|
|
8
|
+
module ClassMethods
|
|
9
|
+
def belongs_to_user(*, **)
|
|
10
|
+
User::Attribute.new(self, *, **, db: true)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def resolvable(*, **)
|
|
14
|
+
User::Attribute.new(self, *, **, db: false)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def with_user(instance, method, user, *args, **, &)
|
|
18
|
+
default = { new: :creator, create: :creator, create!: :creator, update: :updater, update!: :updater, destroy: :destroyer, destroy!: :destroyer }.fetch(method)
|
|
19
|
+
attrs = args.grep(Symbol)
|
|
20
|
+
attrs = [default] if default && attrs.empty?
|
|
21
|
+
other = args - attrs
|
|
22
|
+
hashes = other.grep(Hash)
|
|
23
|
+
params = other.grep(ActionController::Parameters)
|
|
24
|
+
other -= hashes + params
|
|
25
|
+
other.compact_blank!
|
|
26
|
+
options = [attrs.index_with(user), *hashes, *params.map(&:to_hash)].inject(:merge)
|
|
27
|
+
if %i[destroy destroy!].include?(method)
|
|
28
|
+
options.each do |attr, value|
|
|
29
|
+
instance.send("#{attr}=", value)
|
|
30
|
+
end
|
|
31
|
+
instance.send(method)
|
|
32
|
+
else
|
|
33
|
+
instance.send(method, *other, **options, **, &)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def new_with(...)
|
|
38
|
+
with_user(self, :new, ...)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def create_with(...)
|
|
42
|
+
new_with(...).tap(&:save)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def create_with!(...)
|
|
46
|
+
new_with(...).tap(&:save!)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def update_with(...)
|
|
51
|
+
self.class.with_user(self, :update, ...)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def update_with!(...)
|
|
55
|
+
self.class.with_user(self, :update!, ...)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def destroy_with(...)
|
|
59
|
+
self.class.with_user(self, :destroy, ...)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def destroy_with!(...)
|
|
63
|
+
self.class.with_user(self, :destroy!, ...)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def put(attr, value, overwrite: false)
|
|
69
|
+
if respond_to?(:"#{attr}_id")
|
|
70
|
+
send(:"#{attr}=", value) if overwrite || send(:"#{attr}_id").blank?
|
|
71
|
+
elsif respond_to?(attr)
|
|
72
|
+
send(:"#{attr}=", value) if overwrite || send(attr).blank?
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module YiffSpace
|
|
4
|
+
module Concerns
|
|
5
|
+
module UserNameMethods
|
|
6
|
+
extend(ActiveSupport::Concern)
|
|
7
|
+
|
|
8
|
+
module ClassMethods
|
|
9
|
+
def name_to_id(name)
|
|
10
|
+
normalized_name = normalize_name(name)
|
|
11
|
+
Utils::Cache.fetch("uni:#{normalized_name}", expires_in: 4.hours) do
|
|
12
|
+
::User.where("lower(name) = ?", normalized_name).pick(:id)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# @param arguments [Array<String, Array<String>>] a list of names
|
|
17
|
+
# @return [Hash{String => Integer, nil}] a hash of normalized names to user IDs
|
|
18
|
+
def bulk_name_to_id(*arguments)
|
|
19
|
+
names = arguments.flatten.map { |n| normalize_name(n) }
|
|
20
|
+
results = names.index_with { |name| Utils::Cache.fetch("uni:#{name}") }.compact_blank
|
|
21
|
+
missing = names - results.keys
|
|
22
|
+
fetched = ::User.where("lower(name) IN (?)", missing).select(:id, :name).to_h { |u| [normalize_name(u.name), u.id] }
|
|
23
|
+
not_found = (missing - fetched.keys).index_with { nil }
|
|
24
|
+
fetched.each { |name, id| Utils::Cache.write("uni:#{name}", id, expires_in: 4.hours) }
|
|
25
|
+
{ **results, **fetched, **not_found }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def name_or_id_to_id(name)
|
|
29
|
+
return name[1..].to_i if name =~ /\A!\d+\z/
|
|
30
|
+
|
|
31
|
+
::User.name_to_id(name)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def name_or_id_to_id_forced(name)
|
|
35
|
+
return name.to_i if name =~ /\A\d+\z/
|
|
36
|
+
|
|
37
|
+
::User.name_to_id(name)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def id_to_name(user_id)
|
|
41
|
+
RequestStore[:id_name_cache] ||= {}
|
|
42
|
+
return RequestStore[:id_name_cache][user_id] if RequestStore[:id_name_cache].key?(user_id)
|
|
43
|
+
|
|
44
|
+
name = Utils::Cache.fetch("uin:#{user_id}", expires_in: 4.hours) do
|
|
45
|
+
::User.where(id: user_id).pick(:name) || YiffSpace.config.anonymous_user_name.call
|
|
46
|
+
end
|
|
47
|
+
RequestStore[:id_name_cache][user_id] = name
|
|
48
|
+
name
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module YiffSpace
|
|
4
|
+
module Concerns
|
|
5
|
+
module UserResolvableMethods
|
|
6
|
+
def resolvable(ip_addr = nil)
|
|
7
|
+
attr = YiffSpace.config.last_ip_addr_attribute
|
|
8
|
+
User::Resolvable.new(self, ip_addr || (attr && respond_to?(attr) ? send(attr) : nil) || YiffSpace.config.default_ip_address)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def resolve
|
|
12
|
+
self
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def is?(user_or_id)
|
|
16
|
+
id == u2id(user_or_id)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def ==(other)
|
|
20
|
+
return super if other.is_a?(YiffSpace.config.user_class)
|
|
21
|
+
|
|
22
|
+
other.is_a?(YiffSpace.config.user_resolvable_class) && super(other.user)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def ===(other)
|
|
26
|
+
other == YiffSpace.config.user_like_class || super
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def is_a?(other)
|
|
30
|
+
other == YiffSpace.config.user_like_class || super
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
include(YiffSpace::User::ToId)
|
|
34
|
+
|
|
35
|
+
private(:u2id)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require("active_job")
|
|
4
|
+
|
|
5
|
+
module YiffSpace
|
|
6
|
+
module Serializers
|
|
7
|
+
class UserResolvableSerializer < ActiveJob::Serializers::ObjectSerializer
|
|
8
|
+
def serialize(arg)
|
|
9
|
+
super("user" => arg.user&.id, "ip_addr" => arg.ip_addr)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def deserialize(arg)
|
|
13
|
+
YiffSpace.config.user_resolvable_class.new(arg["user"].blank? ? nil : YiffSpace.config.user_class.find(arg["user"]), arg["ip_addr"])
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def klass
|
|
17
|
+
YiffSpace.config.user_resolvable_class
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module YiffSpace
|
|
4
|
+
module User
|
|
5
|
+
# User class must have id_to_name & name_to_id methods, and global u2id method must exist
|
|
6
|
+
class Attribute
|
|
7
|
+
class CircularAliasError < StandardError; end
|
|
8
|
+
class CircularCloneError < StandardError; end
|
|
9
|
+
class DuplicateAttributeError < StandardError; end
|
|
10
|
+
class CloneError < StandardError; end
|
|
11
|
+
attr_reader(:klass, :attribute, :db, :ip, :optional, :clones, :overwrite, :aliases, :ignore_nil, :ar_options)
|
|
12
|
+
|
|
13
|
+
# noinspection RubyUnusedLocalVariable
|
|
14
|
+
def initialize(klass, attribute, db:, ip: !db, optional: !db, clones: [], clone: [], overwrite: false, aliases: [], alias: [], ignore_nil: false, **ar_options)
|
|
15
|
+
Attribute.class_setup(klass)
|
|
16
|
+
@klass = klass
|
|
17
|
+
@attribute = attribute.to_sym
|
|
18
|
+
@db = db
|
|
19
|
+
# noinspection RubySimplifyBooleanInspection
|
|
20
|
+
@ip = ip == true ? :"#{attribute}_ip_addr" : ip
|
|
21
|
+
@optional = optional
|
|
22
|
+
@clones = Array(clones.presence || clone).map(&:to_sym)
|
|
23
|
+
@overwrite = overwrite
|
|
24
|
+
@aliases = Array(aliases.presence || binding.local_variable_get(:alias)).map(&:to_sym)
|
|
25
|
+
@ignore_nil = ignore_nil
|
|
26
|
+
@ar_options = ar_options
|
|
27
|
+
if ar_options.any? && !db # rubocop:disable Style/IfUnlessModifier
|
|
28
|
+
Utils::TraceLogger.warn("UserAttribute", "Unexpected extra options for non-db attribute: #{ar_options.inspect}", ignore: [%r{/concerns/user_methods\.rb}, %r{/logical/user_attribute\.rb}])
|
|
29
|
+
end
|
|
30
|
+
@ar_options[:optional] = optional
|
|
31
|
+
@ar_options[:class_name] = "User"
|
|
32
|
+
validate_options!
|
|
33
|
+
klass.user_attributes[attribute] = self
|
|
34
|
+
define_methods
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def validate_options!
|
|
38
|
+
raise(DuplicateAttributeError, "user attribute #{attribute} is already defined on #{klass}") if klass.user_attributes.key?(attribute)
|
|
39
|
+
|
|
40
|
+
validate_aliases!
|
|
41
|
+
validate_clones!
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def validate_aliases!
|
|
45
|
+
return if aliases.empty?
|
|
46
|
+
raise(CircularAliasError, "aliases must not contain attribute (#{klass}.#{attribute}") if aliases.include?(attribute)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def validate_clones!
|
|
50
|
+
return if clones.empty?
|
|
51
|
+
raise(CircularCloneError, "clones must not contain attribute (#{klass}.#{attribute}") if clones.include?(attribute)
|
|
52
|
+
# TODO: implement clone validation logic
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def validate_value!(value, type, record: nil)
|
|
56
|
+
return if value.nil?
|
|
57
|
+
|
|
58
|
+
case type
|
|
59
|
+
when :user
|
|
60
|
+
return if (optional || ignore_nil) && value.nil?
|
|
61
|
+
return if value.is_a?(::User)
|
|
62
|
+
|
|
63
|
+
if value.is_a?(::UserResolvable)
|
|
64
|
+
return if value.user.is_a?(::User)
|
|
65
|
+
|
|
66
|
+
raise(ArgumentError, "Expected User for UserResolvable.user in #{klass.name}##{attribute}, got #{value.user.inspect} (#{value.user.class.name})")
|
|
67
|
+
end
|
|
68
|
+
value_error("is invalid", "UserLike", attribute, value, record: record)
|
|
69
|
+
when :id
|
|
70
|
+
value_error("is invalid", "Integer", "#{attribute}_id", value, record: record) unless value.is_a?(Integer) || (value.is_a?(String) && /\A\d+\Z/.match?(value))
|
|
71
|
+
when :name
|
|
72
|
+
value_error("is invalid", "String", "#{attribute}_name", value, record: record) unless value.is_a?(String)
|
|
73
|
+
when :ip_addr
|
|
74
|
+
return if value.is_a?(IPAddr)
|
|
75
|
+
|
|
76
|
+
begin
|
|
77
|
+
IPAddr.new(value)
|
|
78
|
+
rescue IPAddr::InvalidAddressError
|
|
79
|
+
value_error("is invalid", "IPAddr", ip, value, record: record)
|
|
80
|
+
end
|
|
81
|
+
else
|
|
82
|
+
raise(ArgumentError, "Not sure how to validate #{type}")
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def value_error(message, expected, attr, value, record: nil)
|
|
87
|
+
raise(ArgumentError, "Expected #{expected} for #{klass.name}.#{attr}=, got #{value.inspect} (#{value.class.name})") unless record.respond_to?(:errors) && record.errors.is_a?(ActiveModel::Errors)
|
|
88
|
+
|
|
89
|
+
record.errors.add(attr, message)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def define_methods # rubocop:disable Metrics/MethodLength
|
|
93
|
+
ua = self
|
|
94
|
+
klass.define_singleton_method(:"#{attribute}_attribute") { ua }
|
|
95
|
+
klass.belongs_to(attribute, **ar_options) if db
|
|
96
|
+
if (klass < ActiveModel::Validations) && ip && !optional
|
|
97
|
+
# klass.validates(attribute, presence: { message: "must exist" }) unless optional
|
|
98
|
+
klass.validates(ip, presence: true, if: -> { send("#{ua.attribute}_id").present? })
|
|
99
|
+
end
|
|
100
|
+
klass.define_method(attribute) do
|
|
101
|
+
if ua.db
|
|
102
|
+
value = super()
|
|
103
|
+
else
|
|
104
|
+
value = instance_variable_get(:"@#{ua.attribute}")
|
|
105
|
+
value = ua.get_clone_value(self) if value.blank?
|
|
106
|
+
end
|
|
107
|
+
return if value.blank?
|
|
108
|
+
|
|
109
|
+
if ua.ip && (ip_addr = send(ua.ip))
|
|
110
|
+
::UserResolvable.new(value, ip_addr)
|
|
111
|
+
else
|
|
112
|
+
value
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
klass.define_method("#{attribute}=") do |value|
|
|
116
|
+
ua.validate_value!(value, :user, record: self)
|
|
117
|
+
if ua.clones.any?
|
|
118
|
+
ua.clones.each do |cattr|
|
|
119
|
+
ua.clone_value(self, cattr, value)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
if value.is_a?(::UserResolvable)
|
|
124
|
+
send("#{ua.ip}=", value.ip_addr) if ua.ip
|
|
125
|
+
value = value.user
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
if ua.db
|
|
129
|
+
super(value)
|
|
130
|
+
else
|
|
131
|
+
instance_variable_set(:"@#{ua.attribute}", value)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
klass.define_method("#{attribute}_id") do
|
|
135
|
+
if ua.db
|
|
136
|
+
read_attribute("#{ua.attribute}_id")
|
|
137
|
+
else
|
|
138
|
+
value = instance_variable_get(:"@#{ua.attribute}")&.id
|
|
139
|
+
value = ua.get_clone_value(self, :id) if value.blank?
|
|
140
|
+
value
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
klass.define_method("#{attribute}_id=") do |value|
|
|
144
|
+
ua.validate_value!(value, :id, record: self)
|
|
145
|
+
if ua.db
|
|
146
|
+
write_attribute("#{ua.attribute}_id", value)
|
|
147
|
+
Utils::TraceLogger.warn("UserAttribute", "#{ua.klass.name}.#{ua.attribute}_id= should not be used when clone is enabled (#{ua.clones.join(', ')})", ignore: [%r{/concerns/user_methods\.rb}, %r{/logical/user_attribute\.rb}]) if ua.clones.any?
|
|
148
|
+
else
|
|
149
|
+
value = ::User.find(value) if value.is_a?(Integer) || value.is_a?(String)
|
|
150
|
+
ua.clones.each do |cattr|
|
|
151
|
+
ua.clone_direct(self, cattr, value)
|
|
152
|
+
end
|
|
153
|
+
instance_variable_set(:"@#{ua.attribute}", value)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
klass.define_method("#{attribute}_name") do
|
|
157
|
+
value = send("#{ua.attribute}_id")
|
|
158
|
+
return "Anonymous" if value.blank?
|
|
159
|
+
|
|
160
|
+
::User.id_to_name(value)
|
|
161
|
+
end
|
|
162
|
+
klass.define_method("#{attribute}_name=") do |value|
|
|
163
|
+
ua.validate_value!(value, :name, record: self)
|
|
164
|
+
value = ::User.name_to_id(value)
|
|
165
|
+
return send("#{ua.attribute}_id=", value) if value.blank?
|
|
166
|
+
|
|
167
|
+
send("#{ua.attribute}=", ::User.find(value))
|
|
168
|
+
end
|
|
169
|
+
if ip
|
|
170
|
+
klass.define_method(ip) do
|
|
171
|
+
if ua.db
|
|
172
|
+
read_attribute(ua.ip)
|
|
173
|
+
else
|
|
174
|
+
value = instance_variable_get(:"@#{ua.ip}")
|
|
175
|
+
value = ua.get_clone_value(self, :ip_addr) if value.blank?
|
|
176
|
+
value
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
klass.define_method("#{ip}=") do |value|
|
|
180
|
+
ua.validate_value!(value, :ip_addr, record: self)
|
|
181
|
+
if ua.db
|
|
182
|
+
write_attribute(ua.ip, value)
|
|
183
|
+
else
|
|
184
|
+
value = value.to_s.strip if value.is_a?(String)
|
|
185
|
+
ua.clones.each do |cattr|
|
|
186
|
+
ua.clone_ip(self, cattr, value)
|
|
187
|
+
end
|
|
188
|
+
instance_variable_set(:"@#{ua.ip}", value)
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
klass.instance_exec do
|
|
193
|
+
scope("for_#{ua.attribute}", ->(value) { where(ua.db && ua.ar_options.key?(:foreign_key) ? ua.ar_options[:foreign_key] : "#{ua.attribute}_id" => u2id(value)) }) unless respond_to?("for_#{ua.attribute}")
|
|
194
|
+
scope("for_#{ua.attribute}_id", ->(value) { where(ua.db && ua.ar_options.key?(:foreign_key) ? ua.ar_options[:foreign_key] : "#{ua.attribute}_id" => value) }) unless respond_to?("for_#{ua.attribute}_id")
|
|
195
|
+
scope("for_#{ua.attribute}_name", ->(value) { where(ua.db && ua.ar_options.key?(:foreign_key) ? ua.ar_options[:foreign_key] : "#{ua.attribute}_id" => YiffSpace.config.user_class.name_to_id(value)) }) unless respond_to?("for_#{ua.attribute}_name")
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
aliases.each do |alias_attr|
|
|
199
|
+
create_alias(alias_attr)
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def get_clone_value(record, type = nil)
|
|
204
|
+
attrs = klass.user_attributes.select { |_attr, options| options.clones.include?(attribute) }
|
|
205
|
+
value = nil
|
|
206
|
+
attrs.each_key do |cattr|
|
|
207
|
+
case type
|
|
208
|
+
when :id
|
|
209
|
+
value = record.send("#{cattr}_id")
|
|
210
|
+
when :ip_addr
|
|
211
|
+
value = record.send(attrs[cattr].ip) if attrs[cattr].ip
|
|
212
|
+
else
|
|
213
|
+
value = record.send(cattr)
|
|
214
|
+
end
|
|
215
|
+
break if value.present?
|
|
216
|
+
end
|
|
217
|
+
value
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def clone_value(record, attr, value)
|
|
221
|
+
return record.send("#{attr}=", value) if overwrite
|
|
222
|
+
|
|
223
|
+
if record.respond_to?(:"#{attr}_id")
|
|
224
|
+
record.send(:"#{attr}=", value) if record.send(:"#{attr}_id").blank?
|
|
225
|
+
elsif record.respond_to?(attr)
|
|
226
|
+
record.send(:"#{attr}=", value) if record.send(attr).blank?
|
|
227
|
+
else
|
|
228
|
+
raise(CloneError, "Not sure how to clone to #{attr}, #{record.class} does not respond_to? #{attr}_id or #{attr}")
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def clone_ip(record, attr, value)
|
|
233
|
+
ip_attr = klass.user_attributes.find { |uattr, _value| uattr == attr }&.second&.ip
|
|
234
|
+
raise(CloneError, "Not sure how to clone ip for #{attr}, no ip attribute defined") unless ip_attr
|
|
235
|
+
|
|
236
|
+
clone_direct(record, ip_attr, value)
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def clone_direct(record, attr, value)
|
|
240
|
+
return record.send("#{attr}=", value) if overwrite
|
|
241
|
+
|
|
242
|
+
raise(CloneError, "Not sure how to clone to #{attr}, #{record.class} does not respond_to? #{attr}") unless record.respond_to?(attr)
|
|
243
|
+
|
|
244
|
+
record.send(:"#{attr}=", value) if record.send(attr).blank?
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def create_alias(attr)
|
|
248
|
+
ua = self
|
|
249
|
+
klass.class_eval do
|
|
250
|
+
define_method(attr) { send(ua.attribute) }
|
|
251
|
+
define_method("#{attr}=") { |value| send("#{ua.attribute}=", value) }
|
|
252
|
+
define_method("#{attr}_id") { send("#{ua.attribute}_id") }
|
|
253
|
+
define_method("#{attr}_id=") { |value| send("#{ua.attribute}_id=", value) }
|
|
254
|
+
define_method("#{attr}_name") { send("#{ua.attribute}_name") }
|
|
255
|
+
define_method("#{attr}_name=") { |value| send("#{ua.attribute}_name=", value) }
|
|
256
|
+
if ua.ip
|
|
257
|
+
define_method("#{attr}_ip_addr") { send(ua.ip) }
|
|
258
|
+
define_method("#{attr}_ip_addr=") { |value| send("#{ua.ip}=", value) }
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def self.class_setup(klass)
|
|
264
|
+
return if klass.method_defined?(:user_attributes)
|
|
265
|
+
|
|
266
|
+
klass.class_eval do
|
|
267
|
+
class_attribute(:user_attributes, default: {})
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module YiffSpace
|
|
4
|
+
module User
|
|
5
|
+
class Current < ActiveSupport::CurrentAttributes
|
|
6
|
+
attribute(:user, :ip_addr, :request)
|
|
7
|
+
|
|
8
|
+
def initialize
|
|
9
|
+
super
|
|
10
|
+
reset
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
after_reset do
|
|
14
|
+
attributes[:user] = YiffSpace.config.anonymous_user_getter.call
|
|
15
|
+
attributes[:ip_addr] = YiffSpace.config.default_ip_address
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def user
|
|
19
|
+
value = super
|
|
20
|
+
return value if value.is_a?(YiffSpace.config.user_resolvable_class) || !value.is_a?(YiffSpace.config.user_class)
|
|
21
|
+
return YiffSpace.config.user_resolvable_class.new(value, ip_addr) if ip_addr.present?
|
|
22
|
+
|
|
23
|
+
value
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def user=(value)
|
|
27
|
+
if value.is_a?(YiffSpace.config.user_resolvable_class)
|
|
28
|
+
self.ip_addr = value.ip_addr
|
|
29
|
+
value = value.user
|
|
30
|
+
end
|
|
31
|
+
super
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.scoped(user, ip_addr = YiffSpace.config.default_ip_address, &)
|
|
35
|
+
set(user: user, ip_addr: ip_addr, &)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.as_system(&)
|
|
39
|
+
scoped(YiffSpace.config.system_user_getter.call, &)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require("rails")
|
|
4
|
+
|
|
5
|
+
module YiffSpace
|
|
6
|
+
module User
|
|
7
|
+
# No app/ of its own - this exists to register the UserResolvableSerializer with ActiveJob.
|
|
8
|
+
class Engine < ::Rails::Engine
|
|
9
|
+
config.root = File.expand_path("../../..", __dir__)
|
|
10
|
+
|
|
11
|
+
initializer("yiffspace.user.serializers") do
|
|
12
|
+
ActiveSupport.on_load(:active_job) do
|
|
13
|
+
ActiveJob::Serializers.add_serializers(Serializers::UserResolvableSerializer)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require("active_support/core_ext/module/delegation")
|
|
4
|
+
require("active_support/core_ext/string/inflections")
|
|
5
|
+
|
|
6
|
+
module YiffSpace
|
|
7
|
+
module User
|
|
8
|
+
class Resolvable
|
|
9
|
+
attr_reader(:user, :ip_addr)
|
|
10
|
+
|
|
11
|
+
def initialize(user, ip_addr, validate: true)
|
|
12
|
+
if user.is_a?(YiffSpace.config.user_resolvable_class)
|
|
13
|
+
Utils::TraceLogger.warn(self.class.name.demodulize, "Unexpectedly received #{user.class.name} for user argument")
|
|
14
|
+
user = recursive_resolve(user)
|
|
15
|
+
end
|
|
16
|
+
@user = user
|
|
17
|
+
ip_addr = ip_addr.to_s if ip_addr.is_a?(IPAddr)
|
|
18
|
+
@ip_addr = ip_addr
|
|
19
|
+
return unless validate
|
|
20
|
+
raise(ArgumentError, "Expected User for user argument, got #{user.inspect} (#{user.class.name})") unless user.is_a?(YiffSpace.config.user_class)
|
|
21
|
+
raise(ArgumentError, "Expected String for ip_addr argument, got #{ip_addr.inspect} (#{ip_addr.class.name})") unless ip_addr.is_a?(String)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
alias resolve user
|
|
25
|
+
|
|
26
|
+
def resolvable(ip_addr = nil)
|
|
27
|
+
@ip_addr = ip_addr if ip_addr.present?
|
|
28
|
+
self
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
delegate(:recursive_resolve, to: :class)
|
|
32
|
+
delegate(:to_param, :serializable_hash, :as_json, :to_json, to: :user)
|
|
33
|
+
delegate_missing_to(:user, allow_nil: true)
|
|
34
|
+
|
|
35
|
+
# needed to allow passing the class around when creating records, active record attempts to call several methods on the provided record's class
|
|
36
|
+
class << self
|
|
37
|
+
delegate_missing_to(:User, allow_nil: false)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.recursive_resolve(resolvable)
|
|
41
|
+
result = resolvable
|
|
42
|
+
result = result.resolve while result.is_a?(YiffSpace.config.user_resolvable_class)
|
|
43
|
+
result
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def ==(other)
|
|
47
|
+
return other == user if other.is_a?(YiffSpace.config.user_class)
|
|
48
|
+
|
|
49
|
+
other.is_a?(YiffSpace.config.user_resolvable_class) && user == other.user
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def ===(other)
|
|
53
|
+
(other == YiffSpace.config.user_like_class && user.is_a?(YiffSpace.config.user_class)) || super
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def is_a?(other)
|
|
57
|
+
(other == YiffSpace.config.user_like_class && user.is_a?(YiffSpace.config.user_class)) || super
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module YiffSpace
|
|
4
|
+
module User
|
|
5
|
+
module ToId
|
|
6
|
+
def u2id(value, klass = YiffSpace.config.user_class)
|
|
7
|
+
value.is_a?(klass) || (klass == YiffSpace.config.user_class && value.is_a?(YiffSpace.config.user_resolvable_class)) ? value.id : value
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require("yiffspace/core")
|
|
4
|
+
require("zeitwerk")
|
|
5
|
+
|
|
6
|
+
loader = Zeitwerk::Loader.for_gem_extension(YiffSpace)
|
|
7
|
+
loader.ignore("#{__dir__}/user/engine.rb")
|
|
8
|
+
loader.setup
|
|
9
|
+
|
|
10
|
+
# Require the engine eagerly so it registers with Rails before the host app's
|
|
11
|
+
# active_support.initialize_per_engine_zeitwerk_loaders initializer runs.
|
|
12
|
+
require_relative("user/engine") if defined?(Rails)
|
|
13
|
+
|
|
14
|
+
module YiffSpace
|
|
15
|
+
class Configuration
|
|
16
|
+
# The application's User model class. Used by User::Current, Search::QueryDSL, etc.
|
|
17
|
+
# Falls back to ::User at call time when nil.
|
|
18
|
+
attr_writer(:user_class)
|
|
19
|
+
|
|
20
|
+
# The application's User model class. Used by User::Resolvable
|
|
21
|
+
# Falls back to ::UserLike at call time when nil.
|
|
22
|
+
attr_writer(:user_like_class)
|
|
23
|
+
|
|
24
|
+
# The application's UserResolvable class. Used by User::Current.
|
|
25
|
+
# Falls back to ::UserResolvable at call time when nil.
|
|
26
|
+
attr_writer(:user_resolvable_class)
|
|
27
|
+
|
|
28
|
+
# The CurrentAttributes class used to access the current user in model concerns.
|
|
29
|
+
# Defaults to YiffSpace::User::Current.
|
|
30
|
+
attr_writer(:current_class)
|
|
31
|
+
|
|
32
|
+
# The default IP address assigned to User::Current when none is present.
|
|
33
|
+
attr_accessor(:default_ip_address)
|
|
34
|
+
|
|
35
|
+
# The `last_ip_addr` attribute of the User model, used by the UserResolvableMethods concern
|
|
36
|
+
attr_accessor(:last_ip_addr_attribute)
|
|
37
|
+
|
|
38
|
+
# The anonymous user's name, can be a proc
|
|
39
|
+
attr_reader(:anonymous_user_name)
|
|
40
|
+
|
|
41
|
+
# Override the proc used to fetch the anonymous user. Must respond to #call.
|
|
42
|
+
# Default: -> { (user_class || ::User).anonymous }
|
|
43
|
+
attr_writer(:anonymous_user_getter)
|
|
44
|
+
|
|
45
|
+
# Override the proc used to fetch the system user. Must respond to #call.
|
|
46
|
+
# Default: -> { (user_class || ::User).system }
|
|
47
|
+
attr_writer(:system_user_getter)
|
|
48
|
+
|
|
49
|
+
def user_class
|
|
50
|
+
@user_class || ::User
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def user_like_class
|
|
54
|
+
@user_like_class || User::Like
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def user_resolvable_class
|
|
58
|
+
@user_resolvable_class || User::Resolvable
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Returns the configured current class, defaulting to YiffSpace::User::Current.
|
|
62
|
+
def current_class
|
|
63
|
+
@current_class || User::Current
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Lazily built: calls user_class (or ::User) at invocation time, not config time.
|
|
67
|
+
def anonymous_user_getter
|
|
68
|
+
@anonymous_user_getter ||= -> { user_class.anonymous }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Lazily built: calls user_class (or ::User) at invocation time, not config time.
|
|
72
|
+
def system_user_getter
|
|
73
|
+
@system_user_getter ||= -> { user_class.system }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def anonymous_user_name=(value)
|
|
77
|
+
@anonymous_user_name = value.is_a?(Proc) ? value : -> { value }
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
YiffSpace.configure do |config|
|
|
83
|
+
config.default_ip_address = "127.0.0.1"
|
|
84
|
+
config.last_ip_addr_attribute = :last_ip_addr
|
|
85
|
+
config.anonymous_user_name = "Anonymous"
|
|
86
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: yiffspace-user
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Donovan_DMC
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 2026-09-04 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: rails
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '7.1'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '7.1'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: request_store
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '1.7'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '1.7'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: yiffspace-core
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: 0.2.0
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: 0.2.0
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: zeitwerk
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '2.6'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '2.6'
|
|
68
|
+
description: The User/Current/Resolvable concerns and utils, for https://yiff.space
|
|
69
|
+
and related projects
|
|
70
|
+
email:
|
|
71
|
+
- hewwo@yiff.rocks
|
|
72
|
+
executables: []
|
|
73
|
+
extensions: []
|
|
74
|
+
extra_rdoc_files: []
|
|
75
|
+
files:
|
|
76
|
+
- CHANGELOG.md
|
|
77
|
+
- LICENSE
|
|
78
|
+
- README.md
|
|
79
|
+
- Rakefile
|
|
80
|
+
- lib/yiffspace/concerns/current_methods.rb
|
|
81
|
+
- lib/yiffspace/concerns/user_class_methods.rb
|
|
82
|
+
- lib/yiffspace/concerns/user_methods.rb
|
|
83
|
+
- lib/yiffspace/concerns/user_name_methods.rb
|
|
84
|
+
- lib/yiffspace/concerns/user_resolvable_methods.rb
|
|
85
|
+
- lib/yiffspace/serializers/user_resolvable_serializer.rb
|
|
86
|
+
- lib/yiffspace/user.rb
|
|
87
|
+
- lib/yiffspace/user/attribute.rb
|
|
88
|
+
- lib/yiffspace/user/current.rb
|
|
89
|
+
- lib/yiffspace/user/engine.rb
|
|
90
|
+
- lib/yiffspace/user/like.rb
|
|
91
|
+
- lib/yiffspace/user/resolvable.rb
|
|
92
|
+
- lib/yiffspace/user/to_id.rb
|
|
93
|
+
- lib/yiffspace/user/version.rb
|
|
94
|
+
homepage: https://yiff.space
|
|
95
|
+
licenses:
|
|
96
|
+
- MIT
|
|
97
|
+
metadata:
|
|
98
|
+
allowed_push_host: https://rubygems.org
|
|
99
|
+
homepage_uri: https://yiff.space
|
|
100
|
+
source_code_uri: https://github.com/YiffSpace/Gem/tree/master/yiffspace-user
|
|
101
|
+
changelog_uri: https://github.com/YiffSpace/Gem/blob/master/yiffspace-user/CHANGELOG.md
|
|
102
|
+
rubygems_mfa_required: 'true'
|
|
103
|
+
rdoc_options: []
|
|
104
|
+
require_paths:
|
|
105
|
+
- lib
|
|
106
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - ">="
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: 3.4.1
|
|
111
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
112
|
+
requirements:
|
|
113
|
+
- - ">="
|
|
114
|
+
- !ruby/object:Gem::Version
|
|
115
|
+
version: '0'
|
|
116
|
+
requirements: []
|
|
117
|
+
rubygems_version: 3.6.2
|
|
118
|
+
specification_version: 4
|
|
119
|
+
summary: The User/Current/Resolvable concerns and utils, for https://yiff.space and
|
|
120
|
+
related projects
|
|
121
|
+
test_files: []
|