lesli 5.1.12 → 5.1.13

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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +3 -0
  3. data/app/assets/stylesheets/lesli/application.css +43 -3
  4. data/app/controllers/lesli/application_devise_controller.rb +9 -5
  5. data/app/controllers/lesli/application_lesli_controller.rb +4 -0
  6. data/app/controllers/lesli/items/attachments_controller.rb +200 -0
  7. data/app/controllers/lesli/items/discussions_controller.rb +94 -67
  8. data/app/controllers/lesli/items/tasks_controller.rb +132 -62
  9. data/app/helpers/lesli/turbo_helper.rb +0 -18
  10. data/app/interfaces/lesli/responder_interface.rb +58 -38
  11. data/app/mailers/lesli/application_lesli_mailer.rb +34 -8
  12. data/app/models/concerns/lesli/account_initializer.rb +11 -3
  13. data/app/models/concerns/lesli/account_logs.rb +5 -16
  14. data/app/models/concerns/lesli/items/activities.rb +29 -33
  15. data/app/models/concerns/lesli/items/attachments.rb +88 -0
  16. data/app/models/concerns/lesli/items/discussions.rb +25 -11
  17. data/app/models/concerns/lesli/items/ownership.rb +27 -0
  18. data/app/models/concerns/lesli/items/tasks.rb +26 -6
  19. data/app/models/concerns/lesli/user_extensions.rb +5 -9
  20. data/app/models/lesli/account.rb +10 -14
  21. data/app/models/lesli/items/activity.rb +25 -2
  22. data/app/models/lesli/items/attachment.rb +117 -0
  23. data/app/models/lesli/items/discussion.rb +6 -2
  24. data/app/models/lesli/items/task.rb +3 -1
  25. data/app/models/lesli/user.rb +12 -21
  26. data/app/views/lesli/abouts/welcome.html.erb +2 -5
  27. data/app/views/lesli/errors/not_found.html.erb +7 -21
  28. data/app/views/lesli/errors/unauthorized.html.erb +11 -27
  29. data/app/views/lesli/layouts/application-devise.html.erb +10 -9
  30. data/app/views/lesli/layouts/application-lesli.html.erb +1 -1
  31. data/app/views/lesli/partials/_application-lesli-assets.html.erb +3 -3
  32. data/app/views/lesli/partials/_application-lesli-header.html.erb +5 -9
  33. data/config/initializers/lesli.rb +4 -1
  34. data/config/locales/translations.en.yml +34 -0
  35. data/config/locales/translations.es.yml +34 -0
  36. data/db/seed/accounts.rb +5 -5
  37. data/db/seed/users.rb +8 -16
  38. data/db/seed/xyz.rb +38 -17
  39. data/lib/lesli/engine.rb +0 -4
  40. data/lib/lesli/router.rb +5 -2
  41. data/lib/lesli/version.rb +2 -2
  42. data/lib/migrate/items/activity_structure.rb +8 -8
  43. data/lib/migrate/items/attachment_structure.rb +9 -14
  44. data/lib/migrate/items/discussion_structure.rb +5 -5
  45. data/lib/migrate/items/task_structure.rb +2 -3
  46. data/lib/migrate/shared/account_structure.rb +1 -1
  47. data/lib/tasks/lesli_tasks.rake +2 -3
  48. data/readme.md +3 -3
  49. data/test/fixtures/lesli_accounts.yml +18 -0
  50. data/test/fixtures/lesli_users.yml +23 -0
  51. metadata +17 -25
  52. data/app/assets/stylesheets/lesli/application.tailwind.css +0 -33
  53. data/config/brakeman.yml +0 -44
  54. data/config/initializers/devise.rb +0 -27
@@ -33,93 +33,163 @@ Building a better future, one line of code at a time.
33
33
  module Lesli
34
34
  module Items
35
35
  class TasksController < ApplicationLesliController
36
- before_action :set_task, only: %i[show update]
37
- before_action :set_taskable, only: %i[index create]
36
+ before_action :set_taskable, only: :create
37
+ before_action :set_task, only: :update
38
38
 
39
- def index
40
- @tasks = task_class
41
- .where(account: current_user.account)
42
- .order(created_at: :desc)
43
- respond_with_lesli(json: @tasks)
39
+ def create
40
+ task = @taskable.add_task!(
41
+ creator: current_user,
42
+ title: task_params[:title]
43
+ )
44
+ refresh_tasks
45
+
46
+ message = I18n.t("lesli.items.tasks.created")
47
+ respond_with_lesli(
48
+ turbo: [
49
+ stream_notification_success(message),
50
+ tasks_stream
51
+ ],
52
+ json: {
53
+ message: message,
54
+ task: task_payload(task)
55
+ },
56
+ status: :created
57
+ )
58
+ rescue ActiveRecord::RecordInvalid => error
59
+ respond_with_task_errors(error.record)
44
60
  end
45
61
 
46
- def show
62
+ def update
63
+ @task.update!(done: !@task.done?)
64
+
65
+ message = task_status_message
66
+ respond_with_lesli(
67
+ turbo: [
68
+ stream_notification_success(message),
69
+ turbo_stream.replace(
70
+ helpers.dom_id(@task, scope_key)
71
+ ) do
72
+ render_task(@task)
73
+ end
74
+ ],
75
+ json: {
76
+ message: message,
77
+ task: task_payload(@task)
78
+ }
79
+ )
80
+ rescue ActiveRecord::RecordInvalid => error
81
+ respond_with_task_errors(error.record)
47
82
  end
48
83
 
49
- def create
50
- task = task_class.new(task_params)
84
+ private
51
85
 
52
- task.user = current_user
53
- task.account = current_user.account
54
- task.taskable = @taskable if @taskable
86
+ def scope_key
87
+ ActiveSupport::Inflector
88
+ .underscore(task_class.name)
89
+ .tr("/", "_")
90
+ end
55
91
 
56
- if task.save
57
- respond_with_lesli(:turbo => [
58
- stream_notification_success("Task created"),
59
- turbo_stream.prepend("#{scope_key}-lesli-items-tasks-list") do
60
- LesliView::Items::Task.new(task, scope_key).render_in(view_context)
61
- end
62
- ])
63
- else
64
- respond_with_lesli(
65
- :turbo => stream_notification_danger(task.errors.full_messages)
66
- )
67
- end
92
+ def task_class
93
+ self.class.module_parent.const_get("Task")
68
94
  end
69
95
 
70
- def update
71
- @task.done = true
72
- @task.save
73
- respond_with_lesli(:turbo => [
74
- stream_notification_success("Task updated #{@taskable}"),
75
- turbo_stream.replace(helpers.dom_id(@task, scope_key)) do
76
- LesliView::Items::Task.new(@task, scope_key).render_in(view_context)
77
- end
78
- ])
96
+ def taskable_class
97
+ allowed_taskable_classes.find do |klass|
98
+ klass.name == params.require(:taskable_type)
99
+ end || raise(
100
+ ActiveRecord::RecordNotFound,
101
+ "Unauthorized taskable type"
102
+ )
79
103
  end
80
104
 
81
- private
105
+ def allowed_taskable_classes
106
+ []
107
+ end
82
108
 
83
- # return a key to identify the engine where the tasks are running
84
- def scope_key = ActiveSupport::Inflector.underscore(task_class.name).tr("/", "_")
109
+ def set_task
110
+ @task = task_class.find(params[:id])
111
+ ensure_taskable_access!(@task.taskable)
112
+ end
85
113
 
86
- # return the task class including the engine where the tasks are running
87
- def task_class = self.class.module_parent.const_get("Task")
114
+ def set_taskable
115
+ @taskable = taskable_class.find(
116
+ params.require(:taskable_id)
117
+ )
118
+ ensure_taskable_access!(@taskable)
119
+ end
88
120
 
89
- # validate we invoke only models that implements Lesli::Item::Tasks concern
90
- def validate_taskable taskable
121
+ def ensure_taskable_access!(taskable)
122
+ allowed = taskable.respond_to?(:lesli_item_accessible_by?) &&
123
+ taskable.lesli_item_accessible_by?(current_user)
91
124
 
92
- # 1. Attempt to resolve the class safely
93
- klass = taskable.safe_constantize
125
+ raise ActiveRecord::RecordNotFound unless allowed
126
+ end
94
127
 
95
- # 2. VALIDATION: Check if the class is allowed to be taskable
96
- # We check if the class exists AND if it includes your specific taskable trait
97
- unless klass && klass.respond_to?(:is_lesli_taskable?) && klass.is_lesli_taskable?
98
- render json: { error: "Unauthorized taskable type" }, status: :forbidden and return
99
- end
128
+ def task_params
129
+ params.require(scope_key.to_sym).permit(:title)
130
+ end
100
131
 
101
- klass
132
+ def render_task(task)
133
+ LesliView::Items::Task
134
+ .new(task, scope_key)
135
+ .render_in(view_context)
102
136
  end
103
137
 
104
- def set_task
105
- @task = task_class.where(account: current_user.account).find(params[:id])
138
+ def task_payload(task)
139
+ {
140
+ id: task.id,
141
+ title: task.title,
142
+ done: task.done?,
143
+ taskable_type: task.taskable_type,
144
+ taskable_id: task.taskable_id,
145
+ creator: {
146
+ id: task.user_id,
147
+ name: task.user.name
148
+ },
149
+ created_at: task.created_at,
150
+ updated_at: task.updated_at
151
+ }
106
152
  end
107
153
 
108
- # Permite filtrar/crear para un actionable específico:
109
- # params: actionable_type=LesliSupport::Ticket&actionable_id=123
110
- def set_taskable
111
- type = params[:taskable_type].presence
112
- id = params[:taskable_id].presence
113
- return if type.blank? || id.blank?
154
+ def refresh_tasks
155
+ association_name =
156
+ @taskable.class.lesli_tasks_association_name
114
157
 
115
- taskable = validate_taskable(type)
158
+ @taskable.public_send(association_name).reset
159
+ end
116
160
 
117
- # 3. Execution: Now it is safe to query
118
- @taskable = taskable.where(account: current_user.account).find_by(id: id)
161
+ def task_status_message
162
+ status = @task.done? ? :completed : :reopened
163
+ I18n.t("lesli.items.tasks.#{status}")
119
164
  end
120
165
 
121
- def task_params
122
- params.require(scope_key.to_sym).permit(:title)
166
+ def tasks_stream(task: nil)
167
+ turbo_stream.replace(
168
+ helpers.dom_id(@taskable, :tasks)
169
+ ) do
170
+ LesliView::Items::Tasks
171
+ .new(@taskable, task: task)
172
+ .render_in(view_context)
173
+ end
174
+ end
175
+
176
+ def respond_with_task_errors(task)
177
+ turbo = [
178
+ stream_notification_danger(
179
+ task.errors.full_messages.to_sentence
180
+ )
181
+ ]
182
+
183
+ if task.new_record?
184
+ refresh_tasks
185
+ turbo << tasks_stream(task: task)
186
+ end
187
+
188
+ respond_with_lesli(
189
+ turbo: turbo,
190
+ json: { errors: task.errors },
191
+ status: :unprocessable_content
192
+ )
123
193
  end
124
194
  end
125
195
  end
@@ -37,24 +37,6 @@ module Lesli
37
37
  link_to(body, url, class: "link", data: { turbo_frame: '_top' })
38
38
  end
39
39
 
40
- # Meta-programming to define flash setter methods dynamically
41
- # success("Everything worked!")
42
- # danger("Oops, there was an error.")
43
- # info("Just an informational message.")
44
- # warning("This is a warning.")
45
- [:info, :success, :warning, :danger].each do |flash_type|
46
- define_method(flash_type) do |message|
47
- flash[flash_type] = message
48
- end
49
- end
50
-
51
- def stream_notification_success(message)
52
- success(message)
53
- turbo_stream.replace("application-lesli-notifications") do
54
- render("lesli/partials/application-lesli-notifications")
55
- end
56
- end
57
-
58
40
  def render_form_stream(partial_path, locals = {})
59
41
 
60
42
  # Detect the current engine's namespace
@@ -33,6 +33,10 @@ Building a better future, one line of code at a time.
33
33
  module Lesli
34
34
  module ResponderInterface
35
35
 
36
+ # Lesli-specific response status used to tell JSON clients that they
37
+ # must execute the action included in the response body.
38
+ ACTION_REQUIRED_STATUS = 490
39
+
36
40
  # Standard json structure for paginated data,
37
41
  # this is compatible for json, html and turbo responses
38
42
  # NOTE: LesliView::Element::Table require this structure
@@ -58,25 +62,37 @@ module Lesli
58
62
  }
59
63
  end
60
64
 
61
- # General response builder
62
- # Support for: turbo, json and html
63
- # Note: Turbo respond support single stream or array of streams
64
- # Usage:
65
- # respond_with(
66
- # turbo: [
67
- # stream_notification_success('ticket creado de forma exitosass')
68
- # stream_notification_warning('ticket creado de forma exitosass')
69
- # ],
70
- # json: {
71
- # message: "it works"
72
- # },
73
- # html: 'lesli/abouts/up'
74
- # )
75
- def respond_with_lesli(turbo:nil, json:nil, html:nil)
65
+ # Build a response for HTML, JSON, or Turbo Stream requests.
66
+ #
67
+ # `html` is passed to `render`. When omitted, Rails renders the current
68
+ # action's template. Use `redirect` for HTML redirects instead of
69
+ # invoking `redirect_to` before calling this method. `status` applies to
70
+ # rendered responses; redirects use `redirect_status`.
71
+ def respond_with_lesli(
72
+ turbo: nil,
73
+ json: nil,
74
+ html: nil,
75
+ redirect: nil,
76
+ status: :ok,
77
+ redirect_status: :see_other
78
+ )
79
+ if html && redirect
80
+ raise ArgumentError, "Use either html: or redirect:, not both"
81
+ end
82
+
76
83
  respond_to do |format|
77
- format.html { render(html) }
78
- format.json { respond_with_json(json) }
79
- format.turbo_stream { render(turbo_stream: turbo) }
84
+ format.html do
85
+ if redirect
86
+ redirect_to(redirect, status: redirect_status)
87
+ elsif html
88
+ render(html, status: status)
89
+ else
90
+ render(status: status)
91
+ end
92
+ end
93
+
94
+ format.json { respond_with_http(status, json) }
95
+ format.turbo_stream { render(turbo_stream: turbo, status: status) }
80
96
  end
81
97
  end
82
98
 
@@ -95,12 +111,13 @@ module Lesli
95
111
  end
96
112
  end
97
113
 
98
- # General error for unauthorized request
99
- # Usage:
100
- # This method is automatically triggered by the
101
- # LesliShield authentication interface, however
102
- # you can use it anywhere by just invoking this method
103
- def respond_with_unauthorized(details = {})
114
+ # Respond with HTTP 403 when an authenticated user does not have the
115
+ # privileges required to perform the requested action.
116
+ #
117
+ # This method is used by LesliShield's authorization interface after
118
+ # authentication has succeeded. `respond_with_unauthorized` remains
119
+ # available as a backwards-compatible alias for existing engines.
120
+ def respond_with_forbidden(details = {})
104
121
 
105
122
  @error_object = {
106
123
  error_role: nil,
@@ -117,11 +134,13 @@ module Lesli
117
134
 
118
135
  respond_to do |format|
119
136
  format.json { respond_with_json_unauthorized(@error_object) }
120
- format.html { render('lesli/errors/unauthorized', status: :unauthorized) }
121
- format.turbo_stream { render('lesli/errors/unauthorized', status: :unauthorized) }
137
+ format.html { render('lesli/errors/unauthorized', status: :forbidden) }
138
+ format.turbo_stream { render('lesli/errors/unauthorized', status: :forbidden) }
122
139
  end
123
140
  end
124
141
 
142
+ alias_method :respond_with_unauthorized, :respond_with_forbidden
143
+
125
144
 
126
145
  # ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~ ~·~
127
146
  # Auxiliar methods for responders
@@ -143,7 +162,7 @@ module Lesli
143
162
 
144
163
  # Return the stream object so you can call:
145
164
  # render turbo_stream: stream_notification_success("Done!")
146
- turbo_stream.update(
165
+ turbo_stream.replace(
147
166
  "application-lesli-notifications",
148
167
  partial: "lesli/partials/application-lesli-notifications"
149
168
  )
@@ -162,7 +181,7 @@ module Lesli
162
181
 
163
182
  # Just a simple json success response for JSON
164
183
  def respond_with_json(payload = nil)
165
- respond_with_http(200, payload)
184
+ respond_with_http(:ok, payload)
166
185
  end
167
186
 
168
187
  # Just a simple json not found response for JSON
@@ -185,19 +204,20 @@ module Lesli
185
204
  end
186
205
 
187
206
  # Just a simple http response for JSON
188
- def respond_with_http(status, payload)
189
- render(
190
- :status => status,
191
- :content_type => 'application/json',
192
- :json => payload.nil? ? "" : payload.to_json
193
- )
207
+ def respond_with_http(status, payload = nil)
208
+ return head(status) if payload.nil?
209
+
210
+ render(:status => status, :json => payload)
194
211
  end
195
212
 
196
- # General method to respond with and order for an action
197
- # This method exists just for compatibility poruposes,
198
- # a refactor is needed for this method
213
+ # Tell a JSON client to execute an action before it can continue.
214
+ # Clients using the Lesli API must handle ACTION_REQUIRED_STATUS and
215
+ # dispatch the action included in the response body.
199
216
  def respond_with_action(action, message = "Action Required")
200
- respond_with_http(490, { :message => message, :action => action })
217
+ respond_with_http(
218
+ ACTION_REQUIRED_STATUS,
219
+ { :message => message, :action => action }
220
+ )
201
221
  end
202
222
  end
203
223
  end
@@ -34,7 +34,12 @@ module Lesli
34
34
  class ApplicationLesliMailer < ActionMailer::Base
35
35
 
36
36
  # Set a dynamic template according to the engine that is sending the email
37
- default(template_path: Lesli.config.mailer.dig(:templates))
37
+ default(
38
+ template_path: (Lesli.config.mailer || {}).fetch(
39
+ :templates,
40
+ "lesli_assets/emails"
41
+ )
42
+ )
38
43
 
39
44
  #after_action :log_mail_requests
40
45
 
@@ -48,9 +53,16 @@ module Lesli
48
53
  end
49
54
 
50
55
  def build_url(path, params = {})
51
- default_url = Rails.application.config.action_mailer.default_url_options
56
+ default_url = mailer_url_options
52
57
  host = default_url[:host].to_s.sub(/\Ahttps?:\/\//, "")
53
- scheme = default_url[:protocol]&.delete(":") || "http"
58
+ scheme = default_url[:protocol].presence&.to_s&.delete(":") || "http"
59
+
60
+ if host.blank?
61
+ raise ArgumentError, <<~MESSAGE.squish
62
+ Action Mailer requires default_url_options[:host] to build
63
+ absolute links in Lesli emails
64
+ MESSAGE
65
+ end
54
66
 
55
67
  URI::Generic.build(
56
68
  scheme: scheme,
@@ -63,19 +75,32 @@ module Lesli
63
75
 
64
76
  protected
65
77
 
66
- def email(params={}, user:nil, to:, subject:, template_name:)
78
+ def email(
79
+ params = {},
80
+ user: nil,
81
+ to:,
82
+ subject:,
83
+ template_name:,
84
+ **headers
85
+ )
67
86
 
68
87
  @params = @params.merge(params)
69
88
 
70
89
  build_app_from_params(params)
71
90
 
72
91
  mail(
73
- to: to,
92
+ to: to,
74
93
  subject: subject,
94
+ **headers,
75
95
  template_name: template_name
76
96
  )
77
97
  end
78
98
 
99
+ def mailer_url_options
100
+ options = Rails.application.config.action_mailer.default_url_options
101
+ (options || {}).to_h.symbolize_keys
102
+ end
103
+
79
104
 
80
105
  private
81
106
 
@@ -88,11 +113,12 @@ module Lesli
88
113
  tag_line: "",
89
114
  }
90
115
 
91
- return if params[:user].blank? || params[:user].class.name != "Lesli::User"
116
+ account = params[:user]&.account
117
+ return if account.blank?
92
118
 
93
119
  @app[:company] = {
94
- id: params[:user].account.id,
95
- name: params[:user].account.name,
120
+ id: account.id,
121
+ name: account.name,
96
122
  #tag_line: params[:user].account.company_tag_line,
97
123
  }
98
124
  end
@@ -39,17 +39,17 @@ module Lesli
39
39
 
40
40
  # platform role for owners
41
41
  owner = self.roles
42
- .create_with({ permission_level: 2147483647 })
42
+ .create_with({ permission_level: 2147483647, active: true })
43
43
  .find_or_create_by(:name => "owner")
44
44
 
45
45
  # platform role for administrators
46
46
  admin = self.roles
47
- .create_with({ permission_level: 100000})
47
+ .create_with({ permission_level: 100000, active: true })
48
48
  .find_or_create_by(name: "admin")
49
49
 
50
50
  # platform role for guests
51
51
  guest = self.roles
52
- .create_with({ permission_level: 10, path_default: "/administration/profile" })
52
+ .create_with({ permission_level: 10, active: true , path_default: "/administration/profile" })
53
53
  .find_or_create_by(name: "guest")
54
54
 
55
55
 
@@ -64,6 +64,7 @@ module Lesli
64
64
 
65
65
  # initialize engines for new accounts
66
66
  def initialize_engines
67
+ initialized_engines = []
67
68
 
68
69
  LesliSystem.engines.each do |engine, data|
69
70
 
@@ -72,6 +73,13 @@ module Lesli
72
73
  # Create an associated account if the attribute is blank
73
74
  engine.constantize::Account.find_or_create_by!(account: self)
74
75
 
76
+ initialized_engines << engine
77
+ end
78
+
79
+ # Emit initialization events only after every engine account exists.
80
+ # In particular, this makes the result independent from the position
81
+ # of LesliAudit in the engine registry.
82
+ initialized_engines.each do |engine|
75
83
  self.log(:account_initialization, "Engine: #{engine} initialized successfully")
76
84
  end
77
85
  end
@@ -35,25 +35,14 @@ module Lesli
35
35
  extend ActiveSupport::Concern
36
36
 
37
37
  def log operation, description=nil, user:nil
38
- return unless defined?(LesliAudit)
39
- log_anonimous(operation, description) unless self.audit
40
- log_account(operation, description) if self.audit
41
- end
42
-
43
- private
44
-
45
- def log_anonimous operation, description
46
- LesliAudit::AccountLog.create({
47
- operation: operation,
48
- description: description
49
- })
50
- end
38
+ return unless defined?(LesliAudit::ActivityAudit)
51
39
 
52
- def log_account operation, description
53
- self.logs.create!({
40
+ LesliAudit::ActivityAudit.record_account!(
41
+ account: self,
42
+ actor: user,
54
43
  operation: operation,
55
44
  description: description
56
- })
45
+ )
57
46
  end
58
47
  end
59
48
  end
@@ -34,9 +34,13 @@ module Lesli
34
34
  module Items
35
35
  module Activities
36
36
  extend ActiveSupport::Concern
37
+ include Lesli::Items::Ownership
37
38
 
38
39
  included do
39
40
  class_attribute :lesli_activities_class, instance_accessor: false
41
+ class_attribute :lesli_activities_association_name,
42
+ instance_accessor: false,
43
+ default: :activities
40
44
  lesli_activities_setup
41
45
  end
42
46
 
@@ -52,13 +56,7 @@ module Lesli
52
56
  end
53
57
 
54
58
  self.lesli_activities_class = klass
55
-
56
- # ✅ Auto-fix table name (works even when engine defines table_name_prefix)
57
- expected_table = lesli_expected_activity_table_name_for(klass)
58
-
59
- if klass.table_name != expected_table
60
- klass.table_name = expected_table
61
- end
59
+ self.lesli_activities_association_name = association_name.to_sym
62
60
 
63
61
  # Set associations
64
62
  # Example:
@@ -66,41 +64,39 @@ module Lesli
66
64
  # has_many :activities, class_name: "LesliSupport::Items::Activity"
67
65
  # end
68
66
  has_many(association_name,
69
- -> { where(deleted_at: nil).order(created_at: :desc) },
67
+ -> {
68
+ where(deleted_at: nil)
69
+ .order(created_at: :desc, id: :desc)
70
+ },
70
71
  as: as,
71
72
  class_name: klass.name,
72
73
  dependent: :destroy
73
74
  )
74
-
75
- # rescue NameError
76
- # raise NameError,
77
- # "Task class not found for #{name}. Expected #{name.deconstantize}::Activity or pass `use:`"
78
- end
79
-
80
- private
81
-
82
- # Build: "lesli_support_item_activities" from "LesliSupport::Item::Activity"
83
- #
84
- # We DO NOT rely on ActiveRecord's table_name_prefix, because that’s what breaks your case.
85
- def lesli_expected_activity_table_name_for(klass)
86
- parts = klass.name.split("::").map(&:underscore) # ["lesli_support", "item", "activity"]
87
- "#{parts.join('_').pluralize}" # "lesli_support_item_activities"
88
75
  end
89
76
  end
90
77
 
91
- def after_save_activities
92
- Termline.warn "You need to define your activity methods in your model"
93
- end
94
-
95
- def activities_create **resource
96
- self.activities.create(
97
- subject_type: self.class.name,
98
- subject_id: self.id,
99
- user_id:self.user_id,
100
- account_id:self.account_id,
101
- **resource
78
+ def record_activity!(
79
+ actor:,
80
+ code:,
81
+ description: nil,
82
+ metadata: {},
83
+ activity_type: :activity
84
+ )
85
+ activity_association.create!(
86
+ actor: actor,
87
+ account: lesli_item_account,
88
+ activity_code: code,
89
+ activity_type: activity_type,
90
+ description: description,
91
+ metadata: metadata
102
92
  )
103
93
  end
94
+
95
+ private
96
+
97
+ def activity_association
98
+ public_send(self.class.lesli_activities_association_name)
99
+ end
104
100
  end
105
101
  end
106
102
  end