biovision 0.1.210414.0 → 0.3.210504.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (127) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +43 -34
  3. data/app/assets/stylesheets/biovision/admin/components.scss +10 -0
  4. data/app/assets/stylesheets/biovision/admin/components/users.scss +4 -0
  5. data/app/assets/stylesheets/biovision/admin/layout.scss +0 -8
  6. data/app/assets/stylesheets/biovision/biovision.scss +8 -0
  7. data/app/assets/stylesheets/biovision/components.scss +1 -0
  8. data/app/assets/stylesheets/biovision/components/filters.scss +41 -0
  9. data/app/assets/stylesheets/biovision/components/forms.scss +16 -0
  10. data/app/assets/stylesheets/biovision/components/quick_search.scss +24 -0
  11. data/app/assets/stylesheets/biovision/default.scss +4 -4
  12. data/app/assets/stylesheets/biovision/themes/default_theme.scss +0 -1
  13. data/app/assets/stylesheets/biovision/themes/default_theme/layout.scss +6 -3
  14. data/app/assets/stylesheets/biovision/themes/default_theme/layout/footer.scss +3 -0
  15. data/app/assets/stylesheets/biovision/themes/default_theme/layout/header.scss +2 -0
  16. data/app/assets/stylesheets/biovision/vars.scss +5 -0
  17. data/app/controllers/admin/components_controller.rb +25 -79
  18. data/app/controllers/admin/dynamic_pages_controller.rb +1 -1
  19. data/app/controllers/admin/index_controller.rb +8 -2
  20. data/app/controllers/admin/navigation_groups_controller.rb +31 -0
  21. data/app/controllers/admin/tokens_controller.rb +15 -0
  22. data/app/controllers/admin/users_controller.rb +33 -3
  23. data/app/controllers/admin_controller.rb +15 -2
  24. data/app/controllers/concerns/crud_entities.rb +13 -13
  25. data/app/controllers/contact_controller.rb +1 -1
  26. data/app/helpers/biovision_components_helper.rb +7 -3
  27. data/app/lib/biovision/components/base/component_parameters.rb +8 -0
  28. data/app/lib/biovision/components/base/component_privileges.rb +26 -17
  29. data/app/lib/biovision/components/base/component_settings.rb +8 -0
  30. data/app/lib/biovision/components/base_component.rb +20 -23
  31. data/app/lib/biovision/components/contact_component.rb +5 -1
  32. data/app/lib/biovision/components/content_component.rb +36 -10
  33. data/app/lib/biovision/components/track_component.rb +1 -1
  34. data/app/lib/biovision/components/users_component.rb +28 -2
  35. data/app/lib/biovision/helpers/data_helper.rb +43 -0
  36. data/app/lib/biovision/migrations/component_migration.rb +54 -0
  37. data/app/mailers/feedback_mailer.rb +14 -0
  38. data/app/models/biovision_component.rb +3 -0
  39. data/app/models/browser.rb +1 -1
  40. data/app/models/code.rb +2 -2
  41. data/app/models/concerns/tree_structure.rb +2 -1
  42. data/app/models/contact_method.rb +1 -1
  43. data/app/models/contact_type.rb +1 -1
  44. data/app/models/dynamic_block.rb +1 -1
  45. data/app/models/dynamic_page.rb +3 -1
  46. data/app/models/feedback_message.rb +7 -1
  47. data/app/models/feedback_response.rb +2 -2
  48. data/app/models/navigation_group.rb +11 -1
  49. data/app/models/role.rb +44 -6
  50. data/app/models/simple_image.rb +1 -1
  51. data/app/models/simple_image_tag.rb +1 -1
  52. data/app/models/token.rb +6 -2
  53. data/app/models/user.rb +25 -10
  54. data/app/uploaders/simple_file_uploader.rb +1 -1
  55. data/app/views/admin/components/_list.html.erb +1 -1
  56. data/app/views/admin/components/entity/_links.html.erb +31 -21
  57. data/app/views/admin/components/links/_base.html.erb +1 -0
  58. data/app/views/admin/components/settings.html.erb +2 -1
  59. data/app/views/admin/components/settings/_settings.html.erb +3 -3
  60. data/app/views/admin/dynamic_blocks/entity/_in_list.html.erb +6 -4
  61. data/app/views/admin/dynamic_blocks/index.html.erb +5 -3
  62. data/app/views/admin/dynamic_blocks/show.html.erb +6 -4
  63. data/app/views/admin/dynamic_pages/_dynamic_page.jbuilder +18 -0
  64. data/app/views/admin/dynamic_pages/entity/_in_list.html.erb +4 -2
  65. data/app/views/admin/dynamic_pages/entity/_in_search.html.erb +7 -0
  66. data/app/views/admin/dynamic_pages/index.html.erb +11 -4
  67. data/app/views/admin/dynamic_pages/search.jbuilder +4 -0
  68. data/app/views/admin/dynamic_pages/show.html.erb +1 -1
  69. data/app/views/admin/index/index.html.erb +7 -5
  70. data/app/views/admin/navigation_group_pages/entity/_in_list.html.erb +26 -0
  71. data/app/views/admin/navigation_groups/entity/_dynamic_pages.html.erb +38 -0
  72. data/app/views/admin/navigation_groups/entity/_in_list.html.erb +6 -4
  73. data/app/views/admin/navigation_groups/index.html.erb +11 -4
  74. data/app/views/admin/navigation_groups/show.html.erb +16 -3
  75. data/app/views/admin/tokens/_form.html.erb +31 -0
  76. data/app/views/admin/tokens/_nav_item.html.erb +6 -0
  77. data/app/views/admin/tokens/entity/_in_list.html.erb +27 -0
  78. data/app/views/admin/tokens/index.html.erb +16 -0
  79. data/app/views/admin/tokens/show.html.erb +26 -0
  80. data/app/views/admin/users/_user.jbuilder +18 -0
  81. data/app/views/admin/users/entity/_in_list.html.erb +3 -1
  82. data/app/views/admin/users/entity/_in_search.html.erb +18 -0
  83. data/app/views/admin/users/index.html.erb +13 -4
  84. data/app/views/admin/users/roles.html.erb +23 -0
  85. data/app/views/admin/users/roles/_component.html.erb +22 -0
  86. data/app/views/admin/users/search.jbuilder +4 -0
  87. data/app/views/admin/users/show.html.erb +14 -2
  88. data/app/views/admin/widgets/_filters.html.erb +15 -0
  89. data/app/views/admin/widgets/_quick_search.html.erb +13 -0
  90. data/app/views/admin/widgets/filters/_flag.html.erb +15 -0
  91. data/app/views/components/content/_dynamic_page.html.erb +6 -10
  92. data/app/views/components/content/_dynamic_page_content.html.erb +14 -0
  93. data/app/views/contact/_form.html.erb +1 -1
  94. data/app/views/feedback_mailer/new_feedback_request.html.erb +11 -0
  95. data/app/views/feedback_mailer/new_feedback_request.text.erb +6 -0
  96. data/app/views/index/index.html.erb +14 -0
  97. data/app/views/layouts/admin.html.erb +0 -1
  98. data/app/views/layouts/application/_footer.html.erb +1 -1
  99. data/app/views/my/index/index.html.erb +26 -0
  100. data/app/views/shared/entity/_time_field.html.erb +6 -0
  101. data/app/views/shared/entity/_track.html.erb +12 -0
  102. data/app/views/shared/entity/_tree_caches.html.erb +8 -1
  103. data/app/views/shared/entity/edit.html.erb +5 -3
  104. data/app/views/shared/forms/_simple_image.html.erb +12 -4
  105. data/config/locales/biovision-ru.yml +14 -1
  106. data/config/locales/components-ru.yml +4 -2
  107. data/config/locales/contact-ru.yml +4 -0
  108. data/config/locales/content-ru.yml +4 -0
  109. data/config/locales/users-ru.yml +27 -5
  110. data/config/routes.rb +14 -4
  111. data/db/migrate/20191228000000_create_biovision_components.rb +2 -0
  112. data/db/migrate/20200224000000_create_track_component.rb +8 -12
  113. data/db/migrate/20200224000010_create_users_component.rb +10 -40
  114. data/db/migrate/20210405000000_create_acl.rb +15 -0
  115. data/db/migrate/{20200529000000_create_content_component.rb → 20210421000000_create_content_component.rb} +3 -18
  116. data/db/migrate/{20210401000000_create_contact_component.rb → 20210421000010_create_contact_component.rb} +1 -22
  117. data/lib/biovision/version.rb +1 -1
  118. metadata +34 -13
  119. data/app/lib/biovision/components/base/privilege_handler.rb +0 -79
  120. data/app/views/admin/components/links/_content.html.erb +0 -9
  121. data/app/views/admin/components/links/_track.html.erb +0 -2
  122. data/app/views/admin/components/links/_users.html.erb +0 -4
  123. data/app/views/admin/components/links/extra/_content.html.erb +0 -0
  124. data/app/views/admin/components/privileges.html.erb +0 -20
  125. data/app/views/admin/components/privileges/_component_user.html.erb +0 -17
  126. data/app/views/admin/components/privileges/_links.html.erb +0 -17
  127. data/app/views/admin/components/privileges/_users.html.erb +0 -23
@@ -10,8 +10,12 @@ module Biovision
10
10
  [SETTING_FEEDBACK_MAIL]
11
11
  end
12
12
 
13
+ def self.default_settings
14
+ { SETTING_FEEDBACK_MAIL => '' }
15
+ end
16
+
13
17
  def self.dependent_models
14
- [ContactMethod, ContactType, FeedbackResponse, FeedbackMessage]
18
+ [FeedbackMessage, FeedbackResponse, ContactType, ContactMethod]
15
19
  end
16
20
 
17
21
  def use_parameters?
@@ -4,24 +4,50 @@ module Biovision
4
4
  module Components
5
5
  # Content
6
6
  class ContentComponent < BaseComponent
7
- def self.privilege_names
8
- %w[content_manager]
9
- end
10
-
11
7
  def self.dependent_models
12
- [DynamicBlock, NavigationGroupPage, NavigationGroup, DynamicPage]
8
+ [DynamicPage, NavigationGroup, NavigationGroupPage, DynamicBlock]
13
9
  end
14
10
 
15
11
  def use_parameters?
16
12
  true
17
13
  end
18
14
 
19
- # @param [ApplicationRecord] entity
20
- # @deprecated use #permit?
21
- def editable?(entity)
22
- return false if entity.nil?
15
+ def use_images?
16
+ true
17
+ end
18
+
19
+ def crud_table_names
20
+ super - %w[navigation_group_pages]
21
+ end
22
+
23
+ def administrative_parts
24
+ %w[navigation_groups dynamic_blocks dynamic_pages]
25
+ end
26
+
27
+ def navigation
28
+ @navigation ||= prepare_navigation
29
+ end
30
+
31
+ private
32
+
33
+ def prepare_navigation
34
+ result = {}
35
+ NavigationGroup.connection.execute(grouped_links_query).each do |row|
36
+ result[row['slug']] = [] unless result.key?(row['slug'])
37
+ result[row['slug']] << { text: row['name'], url: row['url'] }
38
+ end
39
+ result
40
+ end
23
41
 
24
- permit?('edit', entity)
42
+ def grouped_links_query
43
+ <<~SQL
44
+ select g.slug, p.name, p.url
45
+ from "#{NavigationGroupPage.table_name}" gp
46
+ join "#{NavigationGroup.table_name}" g on gp.navigation_group_id = g.id
47
+ join "#{DynamicPage.table_name}" p on gp.dynamic_page_id = p.id
48
+ where p.visible = true
49
+ order by g.slug asc, gp.priority asc
50
+ SQL
25
51
  end
26
52
  end
27
53
  end
@@ -5,7 +5,7 @@ module Biovision
5
5
  # Component for tracking UA and IP
6
6
  class TrackComponent < BaseComponent
7
7
  def self.dependent_models
8
- [IpAddress, Agent, Browser]
8
+ [Browser, Agent, IpAddress]
9
9
  end
10
10
  end
11
11
  end
@@ -33,8 +33,8 @@ module Biovision
33
33
 
34
34
  def self.dependent_models
35
35
  [
36
- Notification, Code, BiovisionComponentUser, UserLanguage,
37
- LoginAttempt, Token, User
36
+ User, Token, LoginAttempt, UserLanguage, BiovisionComponentUser, Code,
37
+ Notification
38
38
  ]
39
39
  end
40
40
 
@@ -51,6 +51,24 @@ module Biovision
51
51
  [SETTING_INVITE_COUNT, SETTING_BOUNCE_COUNT, SETTING_BOUNCE_TIMEOUT]
52
52
  end
53
53
 
54
+ def self.default_settings
55
+ {
56
+ SETTING_OPEN => true,
57
+ SETTING_EMAIL_AS_LOGIN => false,
58
+ SETTING_PHONE_AS_LOGIN => false,
59
+ SETTING_CONFIRM_EMAIL => false,
60
+ SETTING_REQUIRE_EMAIL => false,
61
+ SETTING_USE_PHONE => false,
62
+ SETTING_REQUIRE_PHONE => false,
63
+ SETTING_CONFIRM_PHONE => false,
64
+ SETTING_INVITE_ONLY => false,
65
+ SETTING_USE_INVITES => false,
66
+ SETTING_INVITE_COUNT => 5,
67
+ SETTING_BOUNCE_COUNT => 10,
68
+ SETTING_BOUNCE_TIMEOUT => 3,
69
+ }
70
+ end
71
+
54
72
  # @param [User] user
55
73
  def self.created_user(user)
56
74
  BiovisionComponent.active.pluck(:slug).each do |slug|
@@ -105,6 +123,14 @@ module Biovision
105
123
 
106
124
  user.attributes[attribute_name.to_s]
107
125
  end
126
+
127
+ def crud_table_names
128
+ super - %w[user_languages biovision_component_users]
129
+ end
130
+
131
+ def administrative_parts
132
+ %w[users tokens login_attempts codes notifications]
133
+ end
108
134
  end
109
135
  end
110
136
  end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Biovision
4
+ module Helpers
5
+ # Helper for receiving model data in controllers
6
+ class DataHelper
7
+ attr_reader :model_class
8
+
9
+ # @param [ApplicationRecord] model_class
10
+ def initialize(model_class)
11
+ @model_class = model_class
12
+ end
13
+
14
+ # @param [Integer] page
15
+ # @param [Hash] filters
16
+ def administrative_collection(page = 1, filters = {})
17
+ @filters = filters
18
+ paginates = model_class.respond_to?(:page_for_administration)
19
+ paginates ? administrative_page(page) : administrative_list
20
+ end
21
+
22
+ private
23
+
24
+ def administrative_list
25
+ if model_class.respond_to?(:filtered)
26
+ model_class.filtered(@filters).list_for_administration
27
+ else
28
+ model_class.list_for_administration
29
+ end
30
+ end
31
+
32
+ # @param [Integer] page
33
+ def administrative_page(page)
34
+ reflection = model_class.singleton_method(:page_for_administration)
35
+ if reflection.parameters.include?(%i[key filters])
36
+ model_class.page_for_administration(page, filters: @filters)
37
+ else
38
+ model_class.page_for_administration(page)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Biovision
4
+ module Migrations
5
+ # Mix-ins for repeated component migration parts
6
+ module ComponentMigration
7
+ # Component class
8
+ def component
9
+ @component ||= find_component
10
+ end
11
+
12
+ # Create component tables and roles
13
+ #
14
+ # Creates new BiovisionComponent
15
+ # For each dependent model, calls "create_<table_name>" method
16
+ # Creates roles for new component
17
+ def up
18
+ component.create
19
+ component.dependent_models.each do |model|
20
+ next if model.table_exists?
21
+
22
+ message = "create_#{model.table_name}".to_sym
23
+ send(message) if respond_to?(message, true)
24
+ end
25
+ component[nil].create_roles
26
+ end
27
+
28
+ # Drops tables for each dependent model
29
+ # Removes BiovisionComponent
30
+ def down
31
+ component.dependent_models.reverse.each do |model|
32
+ drop_table model.table_name if model.table_exists?
33
+ end
34
+ BiovisionComponent[component]&.destroy
35
+ end
36
+
37
+ private
38
+
39
+ # Find component class by migration name
40
+ #
41
+ # Method relies on naming like Create<:CamelCaseSlug>Component
42
+ #
43
+ # Example:
44
+ # - Migration class is named CreateFooBarComponent
45
+ # - Component slug is "foo_bar"
46
+ # - Use base component handler to get "FooBarComponent" class
47
+ def find_component
48
+ migration = name.to_s.demodulize.to_s.underscore
49
+ slug = migration.gsub(/\Acreate_/, '').gsub(/_component\z/, '')
50
+ Biovision::Components::BaseComponent.handler_class(slug)
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Mailer for sending feedback requests
4
+ class FeedbackMailer < ApplicationMailer
5
+ # @param [Integer] id
6
+ def new_feedback_request(id)
7
+ @entity = FeedbackMessage.find_by(id: id)
8
+
9
+ key = Biovision::Components::ContactComponent::SETTING_FEEDBACK_MAIL
10
+ receiver = Biovision::Components::ContactComponent[nil].settings[key]
11
+
12
+ mail to: receiver unless @entity.nil? || receiver.blank?
13
+ end
14
+ end
@@ -20,9 +20,12 @@ class BiovisionComponent < ApplicationRecord
20
20
  has_many :biovision_component_users, dependent: :delete_all
21
21
  has_many :simple_images, dependent: :destroy
22
22
  has_many :codes, dependent: :delete_all
23
+ has_many :roles, dependent: :destroy
24
+ has_many :groups, dependent: :destroy
23
25
 
24
26
  scope :active, -> { where(active: true) }
25
27
  scope :list_for_administration, -> { ordered_by_priority }
28
+ scope :list_for_user, -> { active.ordered_by_priority }
26
29
 
27
30
  # Find component by slug
28
31
  #
@@ -28,7 +28,7 @@ class Browser < ApplicationRecord
28
28
 
29
29
  scope :list_for_administration, -> { order('name asc, version asc') }
30
30
 
31
- def self.entity_parameters(*)
31
+ def self.entity_parameters
32
32
  %i[banned mobile name version]
33
33
  end
34
34
  end
data/app/models/code.rb CHANGED
@@ -40,11 +40,11 @@ class Code < ApplicationRecord
40
40
  list_for_administration.page(page)
41
41
  end
42
42
 
43
- def self.entity_parameters(*)
43
+ def self.entity_parameters
44
44
  %i[body quantity]
45
45
  end
46
46
 
47
- def self.creation_parameters(*)
47
+ def self.creation_parameters
48
48
  entity_parameters + %i[user_id biovision_component_id]
49
49
  end
50
50
 
@@ -10,7 +10,8 @@ module TreeStructure
10
10
 
11
11
  before_save { children_cache.uniq! }
12
12
  after_create :cache_parents!
13
- after_save { parent&.cache_children! }
13
+ after_destroy { parent&.cache_children! unless ENV['SKIP_CHILDREN_CACHE'] }
14
+ after_save { parent&.cache_children! unless ENV['SKIP_CHILDREN_CACHE'] }
14
15
 
15
16
  def self.tree(collection)
16
17
  result = {}
@@ -41,7 +41,7 @@ class ContactMethod < ApplicationRecord
41
41
  ContactType[slug]&.contact_methods&.list_for_visitors
42
42
  end
43
43
 
44
- def self.entity_parameters(*)
44
+ def self.entity_parameters
45
45
  %i[contact_type_id language_id name priority value visible]
46
46
  end
47
47
  end
@@ -21,7 +21,7 @@ class ContactType < ApplicationRecord
21
21
  find_by(slug: slug)
22
22
  end
23
23
 
24
- def self.entity_parameters(*)
24
+ def self.entity_parameters
25
25
  %i[slug]
26
26
  end
27
27
  end
@@ -33,7 +33,7 @@ class DynamicBlock < ApplicationRecord
33
33
  visible.find_by(slug: slug)&.body.to_s
34
34
  end
35
35
 
36
- def self.entity_parameters(*)
36
+ def self.entity_parameters
37
37
  %i[body slug visible]
38
38
  end
39
39
 
@@ -36,14 +36,16 @@ class DynamicPage < ApplicationRecord
36
36
  validates_length_of :slug, maximum: SLUG_LIMIT
37
37
  validates_length_of :url, maximum: URL_LIMIT
38
38
 
39
+ scope :visible, -> { where(visible: true) }
39
40
  scope :list_for_administration, -> { included_image.order('slug asc, language_id asc nulls first') }
41
+ scope :search, ->(q) { where('url ilike ? or slug ilike ?', "#{q}%", "#{q}%") unless q.blank? }
40
42
 
41
43
  # @param [String] slug
42
44
  def self.[](slug)
43
45
  find_by(slug: slug)
44
46
  end
45
47
 
46
- def self.entity_parameters(*)
48
+ def self.entity_parameters
47
49
  %i[body language_id name simple_image_id slug url visible]
48
50
  end
49
51
 
@@ -53,7 +53,13 @@ class FeedbackMessage < ApplicationRecord
53
53
  list_for_administration.page(page)
54
54
  end
55
55
 
56
- def self.entity_parameters(*)
56
+ def self.entity_parameters
57
57
  %i[attachment comment email language_id name phone]
58
58
  end
59
+
60
+ def attachment_name
61
+ return if attachment.blank?
62
+
63
+ File.basename(attachment.path)
64
+ end
59
65
  end
@@ -40,11 +40,11 @@ class FeedbackResponse < ApplicationRecord
40
40
  list_for_administration.page(page)
41
41
  end
42
42
 
43
- def self.entity_parameters(*)
43
+ def self.entity_parameters
44
44
  %i[body]
45
45
  end
46
46
 
47
- def self.creation_parameters(*)
47
+ def self.creation_parameters
48
48
  entity_parameters + %i[feedback_message_id]
49
49
  end
50
50
  end
@@ -23,11 +23,21 @@ class NavigationGroup < ApplicationRecord
23
23
 
24
24
  scope :list_for_administration, -> { ordered_by_name }
25
25
 
26
- def self.entity_parameters(*)
26
+ def self.entity_parameters
27
27
  %i[name slug]
28
28
  end
29
29
 
30
30
  def text_for_link
31
31
  name
32
32
  end
33
+
34
+ # @param [DynamicPage] entity
35
+ def add_dynamic_page(entity)
36
+ navigation_group_pages.create(dynamic_page: entity)
37
+ end
38
+
39
+ # @param [DynamicPage] entity
40
+ def remove_dynamic_page(entity)
41
+ navigation_group_pages.where(dynamic_page: entity).destroy_all
42
+ end
33
43
  end
data/app/models/role.rb CHANGED
@@ -12,22 +12,25 @@ class Role < ApplicationRecord
12
12
  include Checkable
13
13
  include HasUuid
14
14
 
15
- SLUG_LIMIT = 50
16
- SLUG_PATTERN = /\A[a-z][_a-z]*[a-z]\z/.freeze
15
+ CACHE_KEY = 'role_cache'
17
16
 
18
17
  belongs_to :biovision_component
19
18
  has_many :role_groups, dependent: :destroy
20
- has_many :user_groups, dependent: :destroy
19
+ has_many :user_roles, dependent: :destroy
21
20
 
22
21
  before_validation { self.slug = slug.to_s.downcase }
23
22
 
24
23
  validates_presence_of :slug
25
24
  validates_uniqueness_of :slug, scope: :biovision_component_id
26
- validates_format_of :slug, with: SLUG_PATTERN
27
25
 
28
26
  # @param [String] slug
29
27
  def self.[](slug)
30
- find_by(slug: slug)
28
+ parts = slug.to_s.split('.')
29
+ criteria = {
30
+ biovision_components: { slug: parts.shift },
31
+ slug: parts.join('.')
32
+ }
33
+ joins(:biovision_component).where(criteria).first
31
34
  end
32
35
 
33
36
  def groups
@@ -37,7 +40,7 @@ class Role < ApplicationRecord
37
40
  end
38
41
 
39
42
  def users
40
- User.where(id: user_ids)
43
+ User.where("data->'role_cache' @> '[?]'::jsonb", id)
41
44
  end
42
45
 
43
46
  def user_ids
@@ -53,4 +56,39 @@ class Role < ApplicationRecord
53
56
  self.user_count = user_ids.count
54
57
  save
55
58
  end
59
+
60
+ # @param [User] user
61
+ def add_to_cache!(user)
62
+ return if user.nil?
63
+
64
+ ids = user.role_ids + [id]
65
+ user.data[CACHE_KEY] = ids.uniq
66
+ user.save
67
+ end
68
+
69
+ # @param [User] user
70
+ def remove_from_cache!(user)
71
+ return if user.nil?
72
+
73
+ new_ids = user.role_ids - [id]
74
+ user.data[CACHE_KEY] = new_ids
75
+ user.save
76
+ end
77
+
78
+ # @param [User] user
79
+ # @param [Hash] options
80
+ def add_user(user, options = { inclusive: true })
81
+ inclusive = !options[:inclusive].blank?
82
+
83
+ user_roles.create(user: user, inclusive: inclusive)
84
+ inclusive ? add_to_cache!(user) : remove_from_cache!(user)
85
+ end
86
+
87
+ # @param [User] user
88
+ def remove_user(user)
89
+ return if user.nil?
90
+
91
+ user_roles.where(user: user).destroy_all
92
+ remove_from_cache!(user)
93
+ end
56
94
  end