yiffspace-core 0.2.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.
Files changed (34) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +46 -0
  3. data/LICENSE +20 -0
  4. data/README.md +27 -0
  5. data/Rakefile +16 -0
  6. data/app/assets/config/yiffspace_manifest.js +1 -0
  7. data/app/assets/stylesheets/yiffspace/application.css +30 -0
  8. data/app/controllers/yiff_space/application_controller.rb +6 -0
  9. data/app/helpers/yiff_space/application_helper.rb +6 -0
  10. data/app/models/yiff_space/application_record.rb +7 -0
  11. data/app/views/layouts/yiff_space/application.html.erb +17 -0
  12. data/app/views/yiff_space/error.html.erb +3 -0
  13. data/lib/yiffspace/concerns/active_record_extensions.rb +45 -0
  14. data/lib/yiffspace/concerns/api_methods.rb +101 -0
  15. data/lib/yiffspace/concerns/attribute_matchers.rb +100 -0
  16. data/lib/yiffspace/concerns/attribute_methods.rb +39 -0
  17. data/lib/yiffspace/concerns/concurrency_methods.rb +20 -0
  18. data/lib/yiffspace/concerns/conditional_includes.rb +43 -0
  19. data/lib/yiffspace/concerns/has_bit_flags.rb +59 -0
  20. data/lib/yiffspace/configuration.rb +43 -0
  21. data/lib/yiffspace/core/version.rb +7 -0
  22. data/lib/yiffspace/core.rb +30 -0
  23. data/lib/yiffspace/engine.rb +13 -0
  24. data/lib/yiffspace/utils/cache.rb +40 -0
  25. data/lib/yiffspace/utils/duration_parser.rb +24 -0
  26. data/lib/yiffspace/utils/helpers.rb +25 -0
  27. data/lib/yiffspace/utils/open_hash.rb +63 -0
  28. data/lib/yiffspace/utils/parameter_builder.rb +121 -0
  29. data/lib/yiffspace/utils/parse_value.rb +174 -0
  30. data/lib/yiffspace/utils/routes.rb +28 -0
  31. data/lib/yiffspace/utils/set_env_constraint.rb +18 -0
  32. data/lib/yiffspace/utils/trace_logger.rb +91 -0
  33. data/lib/yiffspace/utils.rb +6 -0
  34. metadata +135 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 616274421a5c76dd24f3280528dba48846a7d8ffdbf2b6b77a8a9bc6ba243f5e
4
+ data.tar.gz: 49ef59c885784dc479dcc475a4bf55e24bf208a53e9a4288354a68072b2cfd1b
5
+ SHA512:
6
+ metadata.gz: ae0ca898066aeb4781fc565f168189a42601476610b234bdf3fb3431a97dc275e2163a19db67fff2b10718b549bead50a715bcd721635f461d11028ae8b931d4
7
+ data.tar.gz: 7b358ed2a0c766ded8b197596eb768bcdfdc062d1f523f19bb271eda4259f484ce2aeaa7c48192600b1c11d44b91136772eae108a3a9ca594bb445305d475b70
data/CHANGELOG.md ADDED
@@ -0,0 +1,46 @@
1
+ ## Unreleased
2
+
3
+ - **Breaking:** renamed the gem from `yiffspace` to `yiffspace-core`, and split
4
+ it further into standalone gems for complete modularity:
5
+ [`yiffspace-config`](https://github.com/YiffSpace/Gem/tree/master/yiffspace-config)
6
+ (`YiffSpace::ConfigBuilder`),
7
+ [`yiffspace-ext`](https://github.com/YiffSpace/Gem/tree/master/yiffspace-ext)
8
+ (generic Object/Hash/Enumerable/String core extensions + `WhereChain`),
9
+ [`yiffspace-arel`](https://github.com/YiffSpace/Gem/tree/master/yiffspace-arel)
10
+ (Postgres lateral-join/unnest extensions),
11
+ [`yiffspace-images`](https://github.com/YiffSpace/Gem/tree/master/yiffspace-images)
12
+ (avatar/banner URLs),
13
+ [`yiffspace-tables`](https://github.com/YiffSpace/Gem/tree/master/yiffspace-tables)
14
+ (`YiffSpace::Utils::TableBuilder`),
15
+ [`yiffspace-search`](https://github.com/YiffSpace/Gem/tree/master/yiffspace-search)
16
+ (`YiffSpace::Search::*`),
17
+ [`yiffspace-user`](https://github.com/YiffSpace/Gem/tree/master/yiffspace-user)
18
+ (the User/Current/UserResolvable concerns), and
19
+ [`yiffspace-include`](https://github.com/YiffSpace/Gem/tree/master/yiffspace-include)
20
+ (the deprecated global-alias layer). The `yiffspace` gem is now a husk that
21
+ just depends on all of the above, so `gem "yiffspace"` keeps working
22
+ unchanged for apps that want everything; apps that only need a subset can
23
+ depend on the specific gems instead.
24
+ - **Breaking:** extracted the one-time db/fixes system (`YiffSpace::FixTracker`,
25
+ `YiffSpace::FixerTemplate`, `YiffSpace::Configuration::FixerTemplates`, the
26
+ `yiffspace:fixer`/`yiffspace:install:fixes` generators, and the `fixes:*`
27
+ rake tasks) into a separate gem,
28
+ [`yiffspace-fixers`](https://github.com/YiffSpace/Gem/tree/master/yiffspace-fixers).
29
+ Apps using the fixes system need to add that gem alongside `yiffspace`.
30
+ - `YiffSpace::Configuration#fixer_templates`/`#fixer_templates_path` moved to
31
+ `yiffspace-fixers`.
32
+ - The dummy test app no longer needs Postgres - only the fixes table's
33
+ `nulls_not_distinct` index required it, and that lives in `yiffspace-fixers`
34
+ now. Back to plain sqlite3, `docker-compose.yml` removed.
35
+
36
+ ## 0.1.0
37
+
38
+ - **Breaking:** extracted the Logto auth engine (`YiffSpace::Auth::*`) into a
39
+ separate gem, [`yiffspace-auth`](https://github.com/YiffSpace/Auth.rb). Apps
40
+ using auth need to add that gem alongside `yiffspace`.
41
+ - `YiffSpace::Configuration#auth`/`#add_auth`/`#add_default_auth` and the
42
+ `logto_api_*`/`discord_bot_token` settings moved to `yiffspace-auth`.
43
+ - Replaced the dead `YiffSpace::Railtie` with a real `YiffSpace::Engine <
44
+ Rails::Engine`, so the shared `app/` directory (ApplicationController,
45
+ layout, error view, CSS, ActiveJob serializers) actually loads into host
46
+ apps again.
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,27 @@
1
+ # YiffSpace::Core
2
+
3
+ Core Rails engine, `YiffSpace::Configuration`, and shared concerns/utils for
4
+ https://yiff.space and related projects. Everything else in the `yiffspace`
5
+ family builds on this gem.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem "yiffspace-core"
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ ```bash
18
+ $ bundle install
19
+ ```
20
+
21
+ ## Contributing
22
+
23
+ Go away
24
+
25
+ ## License
26
+
27
+ 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,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require("bundler/setup")
4
+
5
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
6
+ load("rails/tasks/engine.rake")
7
+
8
+ load("rails/tasks/statistics.rake")
9
+
10
+ require("bundler/gem_tasks")
11
+
12
+ # CI releases from an already-tagged, already-pushed commit checked out at a detached HEAD, so
13
+ # the default release task's git tag/push step has nothing to do and fails trying to push a
14
+ # branch ref that does not exist in that checkout.
15
+ Rake::Task["release"].clear
16
+ task("release" => "release:rubygem_push")
@@ -0,0 +1 @@
1
+ //= link_directory ../stylesheets/yiffspace .css
@@ -0,0 +1,30 @@
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
+ */
16
+
17
+ body {
18
+ background-color: #2C2F33;
19
+ color: #FFFDD0;
20
+ font-size: 2rem;
21
+ }
22
+
23
+ body div#page {
24
+ text-align: center;
25
+
26
+ ul.permissions-list {
27
+ display: inline-block;
28
+ text-align: left;
29
+ }
30
+ }
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module YiffSpace
4
+ class ApplicationController < ActionController::Base
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module YiffSpace
4
+ module ApplicationHelper
5
+ end
6
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module YiffSpace
4
+ class ApplicationRecord < ActiveRecord::Base
5
+ self.abstract_class = true
6
+ end
7
+ end
@@ -0,0 +1,17 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>YiffSpace</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+
8
+ <%= stylesheet_link_tag("yiffspace/application", media: "all") %>
9
+ </head>
10
+ <body>
11
+
12
+ <div id="page">
13
+ <%= yield %>
14
+ </div>
15
+
16
+ </body>
17
+ </html>
@@ -0,0 +1,3 @@
1
+ <%# locals: (message:) %>
2
+ <h1 class="title">Error</h1>
3
+ <p class="message"><%= message %></p>
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module YiffSpace
4
+ module Concerns
5
+ module ActiveRecordExtensions
6
+ extend(ActiveSupport::Concern)
7
+
8
+ module ClassMethods
9
+ def without_timeout
10
+ original = statement_timeout
11
+ connection.execute("SET STATEMENT_TIMEOUT = 0") unless Rails.env.test?
12
+ yield
13
+ ensure
14
+ connection.execute("SET STATEMENT_TIMEOUT = #{original}") unless Rails.env.test?
15
+ end
16
+
17
+ def with_timeout(time, default_value = nil)
18
+ original = statement_timeout
19
+ connection.execute("SET STATEMENT_TIMEOUT = #{time}") unless Rails.env.test?
20
+ yield
21
+ rescue ::ActiveRecord::StatementInvalid => e
22
+ Rails.logger.warn("Statement timeout exceeded: #{e.message}")
23
+ default_value
24
+ ensure
25
+ connection.execute("SET STATEMENT_TIMEOUT = #{original}") unless Rails.env.test?
26
+ end
27
+
28
+ def statement_timeout
29
+ YiffSpace.config.application_record_class.connection.select_one("SELECT setting FROM pg_settings WHERE name = 'statement_timeout'")["setting"].to_i
30
+ end
31
+
32
+ # CrossJoinLateral, LeftJoinLateral, nil
33
+ def unnest(column, name: column.singularize, type: ::Arel::Nodes::LeftJoinLateral)
34
+ function = ::Arel::Nodes::NamedFunction.new("unnest", [arel(column)], name)
35
+ return function if type.nil?
36
+
37
+ joins(type.new(
38
+ function,
39
+ ::Arel::Nodes::On.new(Arel.sql("TRUE")),
40
+ ))
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module YiffSpace
4
+ module Concerns
5
+ module ApiMethods
6
+ class NotVisibleError < StandardError; end
7
+ extend(ActiveSupport::Concern)
8
+
9
+ module ClassMethods
10
+ def available_includes
11
+ []
12
+ end
13
+
14
+ def multiple_includes
15
+ reflections.select { |_, v| v.macro == :has_many }.keys.map(&:to_sym)
16
+ end
17
+
18
+ def associated_models(name)
19
+ if reflections[name].options[:polymorphic]
20
+ reflections[name].active_record.try(:model_types) || []
21
+ else
22
+ [reflections[name].class_name]
23
+ end
24
+ end
25
+ end
26
+
27
+ delegate(:available_includes, to: :class)
28
+
29
+ # XXX deprecated, shouldn't expose this as an instance method.
30
+ def api_attributes(user)
31
+ policy = Pundit.policy(user, self) || ApplicationPolicy.new(user, self)
32
+ policy.api_attributes
33
+ end
34
+
35
+ # XXX deprecated, shouldn't expose this as an instance method.
36
+ def html_data_attributes(user)
37
+ policy = Pundit.policy(user, self) || ApplicationPolicy.new(user, self)
38
+ policy.html_data_attributes
39
+ end
40
+
41
+ def process_api_attributes(options, underscore: false)
42
+ options[:methods] ||= []
43
+ attributes, methods = api_attributes(options[:user]).partition { |attr| has_attribute?(attr) }
44
+ methods += options[:methods]
45
+ if underscore && options[:only].blank?
46
+ options[:only] = attributes + methods
47
+ else
48
+ options[:only] ||= attributes + methods
49
+ end
50
+
51
+ attributes &= options[:only]
52
+ methods &= options[:only]
53
+
54
+ options[:only] = attributes
55
+ options[:methods] = methods
56
+
57
+ options.delete(:methods) if options[:methods].empty?
58
+ options
59
+ end
60
+
61
+ def serializable_hash(options = {})
62
+ options ||= {}
63
+ options[:user] ||= CurrentUser.user || User.anonymous
64
+ return :not_visible unless visible?(options[:user])
65
+
66
+ if options[:only].is_a?(String)
67
+ options.delete(:methods)
68
+ options.delete(:include)
69
+ options.merge!(ParameterBuilder.serial_parameters(options[:only], self, options))
70
+ if options[:only].include?("_")
71
+ options[:only].delete("_")
72
+ options = process_api_attributes(options, underscore: true)
73
+ end
74
+ else
75
+ options = process_api_attributes(options)
76
+ end
77
+ options[:only] += [SecureRandom.hex(6)]
78
+
79
+ hash = super
80
+ hash.transform_keys! { |key| key.delete("?").delete_prefix("apionly_") }
81
+ deep_reject_hash(hash) { |_, v| v == :not_visible }
82
+ end
83
+
84
+ def visible?(_user)
85
+ true
86
+ end
87
+
88
+ def deep_reject_hash(hash, &block)
89
+ hash.each_with_object({}) do |(key, value), result|
90
+ if value.is_a?(Hash)
91
+ result[key] = deep_reject_hash(value, &block)
92
+ elsif value.is_a?(Array)
93
+ result[key] = value.map { |v| v.is_a?(Hash) ? deep_reject_hash(v, &block) : v }.reject { |i| block.call(nil, i) }
94
+ elsif !block.call(key, value)
95
+ result[key] = value
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module YiffSpace
4
+ module Concerns
5
+ module AttributeMatchers
6
+ def boolean_attribute_matches(attribute, value, &)
7
+ return all if value.nil?
8
+
9
+ if value.to_s.truthy?
10
+ value = true
11
+ elsif value.to_s.falsy?
12
+ value = false
13
+ else
14
+ raise(ArgumentError, "value must be truthy or falsy")
15
+ end
16
+
17
+ q = block_given? ? yield : all
18
+ q.where(attribute => value)
19
+ end
20
+
21
+ # range: "5", ">5", "<5", ">=5", "<=5", "5..10", "5,6,7"
22
+ def numeric_attribute_matches(attribute, value, &)
23
+ return all if value.nil?
24
+
25
+ value = value.to_s.strip
26
+ column = QueryHelper.get_column(attribute, table_name)
27
+ parsed_range = ParseValue.range(value, column.type)
28
+
29
+ add_range_relation(parsed_range, attribute, &)
30
+ end
31
+
32
+ # datetime columns return ActiveSupport::TimeWithZone
33
+ def datetime_attribute_matches(attribute, range, &)
34
+ numeric_attribute_matches(attribute, range, &)
35
+ end
36
+
37
+ def add_range_relation(arr, field, &)
38
+ return all if arr.nil?
39
+
40
+ q = block_given? ? yield : all
41
+ case arr[0]
42
+ when :eq
43
+ if arr[1].is_a?(Time)
44
+ q.where.between(field => arr[1].all_day)
45
+ else
46
+ q.where(field => arr[1])
47
+ end
48
+ when :gt
49
+ q.where.gt(field => arr[1])
50
+ when :gte
51
+ q.where.gte(field => arr[1])
52
+ when :lt
53
+ q.where.lt(field => arr[1])
54
+ when :lte
55
+ q.where.lte(field => arr[1])
56
+ when :in
57
+ q.where.in(field => arr[1])
58
+ when :between
59
+ q.where.between(field => arr[1]..arr[2])
60
+ else
61
+ q.none
62
+ end
63
+ end
64
+
65
+ def text_attribute_matches(attribute, value, convert_to_wildcard: false, &)
66
+ return all if value.nil?
67
+
68
+ value = "*#{value}*" if convert_to_wildcard && value.exclude?("*")
69
+ q = block_given? ? yield : all
70
+ if value.is_a?(Array)
71
+ q.where.ilike_any(attribute => value)
72
+ elsif value =~ /\*/
73
+ q.where.ilike(attribute => value)
74
+ else
75
+ q.where.tsquery(attribute => value)
76
+ end
77
+ end
78
+
79
+ def ip_attribute_matches(attribute, value, &)
80
+ return all if value.nil?
81
+
82
+ q = block_given? ? yield : all
83
+ q.where.inet_contained_within_or_equals(attribute => value)
84
+ end
85
+
86
+ def attribute_present(attribute, value, &)
87
+ return all if value.nil?
88
+
89
+ q = block_given? ? yield : all
90
+ if value.to_s.truthy?
91
+ q.where.not(attribute => nil)
92
+ elsif value.to_s.falsy?
93
+ q.where(attribute => nil)
94
+ else
95
+ q.none
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module YiffSpace
4
+ module Concerns
5
+ module AttributeMethods
6
+ extend(ActiveSupport::Concern)
7
+
8
+ module ClassMethods
9
+ # Defines `<attribute>_string`, `<attribute>_string=`, and `<attribute>=`
10
+ # methods for converting an array attribute to or from a string.
11
+ #
12
+ # The `<attribute>=` setter parses strings into an array using the
13
+ # `parse` regex. The resulting strings can be converted to another type
14
+ # with the `cast` option.
15
+ def array_attribute(name, parse: /[^[:space:]]+/, join_character: " ", cast: :itself)
16
+ define_method("#{name}_string") do
17
+ send(name).join(join_character)
18
+ end
19
+
20
+ define_method("#{name}_string=") do |value|
21
+ raise(ArgumentError, "#{name} must be a String") unless value.respond_to?(:to_str)
22
+
23
+ send("#{name}=", value)
24
+ end
25
+
26
+ define_method("#{name}=") do |value|
27
+ if value.respond_to?(:to_str)
28
+ super(value.to_str.scan(parse).flatten.map(&cast))
29
+ elsif value.respond_to?(:to_a)
30
+ super(value.to_a)
31
+ else
32
+ raise(ArgumentError, "#{name} must be a String or an Array")
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module YiffSpace
4
+ module Concerns
5
+ module ConcurrencyMethods
6
+ extend(ActiveSupport::Concern)
7
+
8
+ class_methods do
9
+ def parallel_find_each(**, &)
10
+ # XXX We may deadlock if a transaction is open; do a non-parallel each.
11
+ return find_each(&) if connection.transaction_open?
12
+
13
+ find_in_batches(error_on_ignore: true, **) do |batch|
14
+ batch.parallel_each(&)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module YiffSpace
4
+ module Concerns
5
+ module ConditionalIncludes
6
+ extend(ActiveSupport::Concern)
7
+
8
+ module ClassMethods
9
+ def html_includes(request, *)
10
+ includes_if(request.format.html?, *)
11
+ end
12
+
13
+ def includes_if(condition, *)
14
+ return all unless condition
15
+
16
+ includes(*)
17
+ end
18
+
19
+ def includes_unless(condition, *)
20
+ return all if condition
21
+
22
+ includes(*)
23
+ end
24
+
25
+ def html_preload(request, *)
26
+ preload_if(request.format.html?, *)
27
+ end
28
+
29
+ def preload_if(condition, *)
30
+ return all unless condition
31
+
32
+ preload(*)
33
+ end
34
+
35
+ def preload_unless(condition, *)
36
+ return all if condition
37
+
38
+ preload(*)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module YiffSpace
4
+ module Concerns
5
+ module HasBitFlags
6
+ extend(ActiveSupport::Concern)
7
+
8
+ module ClassMethods
9
+ # NOTE: the ordering of attributes has to be fixed
10
+ # new attributes should be appended to the end.
11
+ def has_bit_flags(attributes, options = {})
12
+ field = options[:field] || :bit_flags
13
+
14
+ define_singleton_method("flag_value_for") do |key|
15
+ value = attributes[key.to_s]
16
+ return value if value
17
+
18
+ raise(ArgumentError, "Invalid flag: #{key}")
19
+ end
20
+
21
+ attributes.each do |name, value|
22
+ scope(name.to_sym, -> { where.has_bits(field => value) })
23
+ define_method(name) do
24
+ send(field) & value == value
25
+ end
26
+
27
+ define_method("#{name}=") do |val|
28
+ if val.to_s =~ /[t1y]/
29
+ send("#{field}=", send(field) | value)
30
+ else
31
+ send("#{field}=", send(field) & ~value)
32
+ end
33
+ end
34
+
35
+ define_method("#{name}_was") do
36
+ send("#{field}_was") & value == value
37
+ end
38
+
39
+ define_method("#{name}_before_last_save") do
40
+ send("#{field}_before_last_save") & value == value
41
+ end
42
+
43
+ alias_method("#{name}?", name)
44
+ alias_method("#{name}_before_last_save?", "#{name}_before_last_save")
45
+ alias_method("#{name}_was?", "#{name}_was")
46
+
47
+ define_method("#{name}_changed?") do
48
+ send("#{name}_was") != send(name)
49
+ end
50
+
51
+ define_method("saved_change_to_#{name}?") do
52
+ send("#{name}_before_last_save") != send(name)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module YiffSpace
4
+ class Configuration
5
+ # Maximum number of comma-separated values allowed in a multi-value query parameter.
6
+ # Used by ParseValue.range and (in yiffspace-search) QueryBuilder.
7
+ attr_reader(:max_multi_count)
8
+
9
+ # Redis URL used by Utils::Cache for direct Redis connections.
10
+ attr_reader(:redis_url)
11
+
12
+ # The application's ApplicationRecord class. Used by Concerns::ActiveRecordExtensions & (in
13
+ # yiffspace-tables) Utils::TableBuilder.
14
+ # Falls back to ::ApplicationRecord at call time when nil.
15
+ attr_writer(:application_record_class)
16
+
17
+ # The application's ApplicationController class. Used by Utils::Helpers & (in
18
+ # yiffspace-tables) Utils::TableBuilder.
19
+ # Falls back to ::ApplicationController at call time when nil.
20
+ attr_writer(:application_controller_class)
21
+
22
+ def initialize
23
+ @max_multi_count = -> { 100 }
24
+ @redis_url = -> {}
25
+ end
26
+
27
+ def redis_url=(value)
28
+ @redis_url = value.is_a?(Proc) ? value : -> { value }
29
+ end
30
+
31
+ def max_multi_count=(value)
32
+ @max_multi_count = value.is_a?(Proc) ? value : -> { value }
33
+ end
34
+
35
+ def application_record_class
36
+ @application_record_class || ::ApplicationRecord
37
+ end
38
+
39
+ def application_controller_class
40
+ @application_controller_class || ::ApplicationController
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module YiffSpace
4
+ module Core
5
+ VERSION = "0.2.0"
6
+ end
7
+ end