Class: Decidim::ActionLog

Inherits:
ApplicationRecord show all
Includes:
ScopableParticipatorySpace
Defined in:
decidim-core/app/models/decidim/action_log.rb

Overview

This class represents an action of a user on a resource. It is used for transparency reasons, to log all actions so all other users can see the actions being performed.

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.for_adminObject

A scope that filters all the logs that should be visible at the admin panel.



126
127
128
# File 'decidim-core/app/models/decidim/action_log.rb', line 126

def self.for_admin
  where(visibility: %w(admin-only all))
end

.lazy_relation(id_method, klass_name, cache) ⇒ Object

Returns a Batchloader for a given class to avoid N+1 queries.

Since ActionLogs are related to many different resources, loading a collection of them would trigger a lot of N+1 queries. We are using BatchLoader to accumulate and group all the resource by their class and only loading them when it is necessary.



247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'decidim-core/app/models/decidim/action_log.rb', line 247

def self.lazy_relation(id_method, klass_name, cache)
  klass = klass_name.constantize
  BatchLoader.for(id_method).batch(cache:, key: klass.name.underscore) do |relation_ids, loader|
    scope = klass.where(id: relation_ids)

    scope = if klass.include?(Decidim::HasComponent)
              scope.where(id: relation_ids).includes(:component).where.not(decidim_components: { published_at: nil })
            elsif klass.reflect_on_association(:organization)
              scope.where(id: relation_ids).includes(:organization)
            elsif klass_name == "Decidim::Comments::Comment"
              scope.where(id: relation_ids).includes([:moderation, :root_commentable, :user_group])
            else
              scope
            end

    scope = scope.published if klass.include?(Decidim::Publicable)

    scope.each { |relation| loader.call(relation.id, relation) }
  end
end

.private_resource_typesObject



149
150
151
152
153
154
155
# File 'decidim-core/app/models/decidim/action_log.rb', line 149

def self.private_resource_types
  @private_resource_types ||= %w(
    Decidim::Budgets::Order
  ).select do |klass|
    klass.safe_constantize.present?
  end
end

.public_resource_typesObject

All the resource types that are eligible to be included as an activity.



131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'decidim-core/app/models/decidim/action_log.rb', line 131

def self.public_resource_types
  @public_resource_types ||= %w(
    Decidim::Accountability::Result
    Decidim::Blogs::Post
    Decidim::Comments::Comment
    Decidim::Debates::Debate
    Decidim::Meetings::Meeting
    Decidim::Proposals::Proposal
    Decidim::Surveys::Survey
    Decidim::Assembly
    Decidim::Conference
    Decidim::Initiative
    Decidim::ParticipatoryProcess
  ).select do |klass|
    klass.safe_constantize.present?
  end
end

.publicable_exceptionsObject



162
163
164
165
166
167
168
# File 'decidim-core/app/models/decidim/action_log.rb', line 162

def self.publicable_exceptions
  @publicable_exceptions = %w(
    Decidim::Blogs::Post
  ).select do |klass|
    klass.safe_constantize.present?
  end
end

.publicable_public_resource_typesObject



157
158
159
160
# File 'decidim-core/app/models/decidim/action_log.rb', line 157

def self.publicable_public_resource_types
  @publicable_public_resource_types ||= public_resource_types
                                        .select { |klass| klass.constantize.column_names.include?("published_at") } - publicable_exceptions
end

.ransackable_associations(_auth_object = nil) ⇒ Object



184
185
186
# File 'decidim-core/app/models/decidim/action_log.rb', line 184

def self.ransackable_associations(_auth_object = nil)
  %w(user)
end

.ransackable_attributes(auth_object = nil) ⇒ Object



178
179
180
181
182
# File 'decidim-core/app/models/decidim/action_log.rb', line 178

def self.ransackable_attributes(auth_object = nil)
  return [] unless auth_object&.admin?

  %w(created_at)
end

.ransackable_scopes(auth_object = nil) ⇒ Object



170
171
172
173
174
175
176
# File 'decidim-core/app/models/decidim/action_log.rb', line 170

def self.ransackable_scopes(auth_object = nil)
  base = [:with_resource_type]
  return base unless auth_object&.admin?

  # Add extra scopes for admins for the admin panel searches
  base + [:with_participatory_space]
end

Instance Method Details

#component_lazy(cache: true) ⇒ Object

Lazy loads the ‘component` association through BatchLoader, can be used as a regular object.



197
198
199
# File 'decidim-core/app/models/decidim/action_log.rb', line 197

def component_lazy(cache: true)
  self.class.lazy_relation(decidim_component_id, "Decidim::Component", cache)
end

#log_presenter_class_for(log_type) ⇒ Object

Public: Finds the correct presenter class for the given ‘log_type` and the related `resource_type`. If no specific presenter can be found, it falls back to `Decidim::Log::BasePresenter`

log_type - a Symbol representing the log

Returns a Class.



235
236
237
238
239
# File 'decidim-core/app/models/decidim/action_log.rb', line 235

def log_presenter_class_for(log_type)
  resource_type.constantize.log_presenter_class_for(log_type)
rescue NameError
  Decidim::Log::BasePresenter
end

#organization_lazy(cache: true) ⇒ Object

Lazy loads the ‘organization` association through BatchLoader, can be used as a regular object.



203
204
205
# File 'decidim-core/app/models/decidim/action_log.rb', line 203

def organization_lazy(cache: true)
  self.class.lazy_relation(decidim_organization_id, "Decidim::Organization", cache)
end

#participatory_space_lazy(cache: true) ⇒ Object

Lazy loads the ‘participatory_space` association through BatchLoader, can be used as a regular object.



215
216
217
218
219
220
# File 'decidim-core/app/models/decidim/action_log.rb', line 215

def participatory_space_lazy(cache: true)
  return if participatory_space_id.blank? || participatory_space_type.blank?
  return resource_lazy if participatory_space_id == resource_id && participatory_space_type == resource_type

  self.class.lazy_relation(participatory_space_id, participatory_space_type, cache)
end

#readonly?Boolean

Overwrites the method so that records cannot be modified.

Returns a Boolean.

Returns:

  • (Boolean)


191
192
193
# File 'decidim-core/app/models/decidim/action_log.rb', line 191

def readonly?
  !new_record?
end

#resource_lazy(cache: true) ⇒ Object

Lazy loads the ‘resource` association through BatchLoader, can be used as a regular object.



224
225
226
# File 'decidim-core/app/models/decidim/action_log.rb', line 224

def resource_lazy(cache: true)
  self.class.lazy_relation(resource_id, resource_type, cache)
end

#user_lazy(cache: true) ⇒ Object

Lazy loads the ‘user` association through BatchLoader, can be used as a regular object.



209
210
211
# File 'decidim-core/app/models/decidim/action_log.rb', line 209

def user_lazy(cache: true)
  self.class.lazy_relation(decidim_user_id, "Decidim::User", cache)
end

#visible_for?(user) ⇒ Boolean

Whether this activity or log is visible for a given user (can also be nil)

Returns:

  • (Boolean)


269
270
271
272
273
274
275
276
277
278
279
# File 'decidim-core/app/models/decidim/action_log.rb', line 269

def visible_for?(user)
  resource_lazy.present? &&
    participatory_space_lazy.present? &&
    !resource_lazy.try(:deleted?) &&
    !resource_lazy.try(:hidden?) &&
    (participatory_space_lazy.try(:is_transparent?) || !resource_lazy.respond_to?(:can_participate?) || resource_lazy.try(:can_participate?, user))
rescue NameError => e
  Rails.logger.warn "Failed resource for #{self.class.name}(id=#{id}): #{e.message}"

  false
end