Подтвердить что ты не робот

Система аудита Rails с ActiveResource и ActiveRecord

У меня огромный проект с моделями ActiveRecord и ActiveResource. Мне нужно реализовать регистрацию активности пользователя с этими моделями, а также регистрировать изменения атрибутов модели (сохранить состояние объекта или что-то подобное). Изменения могут выполняться пользователями или задачами cron rake.

У меня также должна быть возможность искать любые данные по дате, любое поле..etc

Будет приятно также генерировать читаемые сообщения с последней активностью, например

  • Пользователь Bob сменил пароль на * и отправил по электронной почте ** в 2011-08-12 08:12
  • Персонал Джефф добавил нового партнера: Название компании в 2011-08-12 08:13
  • Удаленный продукт администратора Jack: Название продукта в 2011-09-12 11:11
  • Клиент Сэм заказал новую услугу: Название службы в 2011-09-12 11:12

Кто-нибудь реализует такой журнал? Идеи? Советы?

Я должен использовать драгоценные камни или могу ли я сделать всю логику с наблюдателями, не меняющими модели?


Мне понравился gem https://github.com/airblade/paper_trail может ли кто-нибудь сказать, как я могу заставить его работать с activeresource?

4b9b3361

Ответ 1

Драгоценности act_as_audited должны хорошо работать для вас:
https://github.com/collectiveidea/acts_as_audited

И что касается ActiveResource, он также будет моделью в каком-то другом приложении. Вы можете использовать камень на стороне сервера, и вам не нужно его проверять на стороне клиента. Все операции CRUD с использованием ActiveResource, наконец, будут переведены на операции CRUD на ActiveRecord (на стороне сервера).

Поэтому, вероятно, вам нужно взглянуть на него издалека, и такое же решение будет применяться в обоих случаях, но в разных местах.

Ответ 2

Вы ищете

https://github.com/collectiveidea/acts_as_audited

Немногие проекты с открытым исходным кодом используют этот плагин, который я считаю Red Mine, а также The Foreman.

Изменить: К сожалению, он может выполнять только ActiveRecord, а не ActiveResource.

Ответ 3

Fivell, я только что увидел этот вопрос и не успел обработать изменения сегодня вечером до того, как истекает срок, и я дам вам свой код аудита, который работает с ActiveRecord, и должен работать с ActiveResource, возможно, с несколькими (я не использую ARes достаточно часто, чтобы знать, что небрежно). Я знаю, что обратные вызовы, которые мы используем, есть, но я не уверен, что ARes имеет отслеживание атрибутов changes ActiveRecord.

Этот код регистрирует каждый CREATE/UPDATE/DELETE для всех моделей (кроме CREATEs в модели журнала аудита и любых других исключений, которые вы указываете) с изменениями, хранящимися как JSON. Очищенная обратная трассировка также сохраняется, поэтому вы можете определить, какой код внес изменения (это фиксирует любую точку вашего MVC, а также задачи рейка и использование консоли).

Этот код работает для использования консоли, рейк-задач и http-запросов, хотя обычно только последний регистрирует текущего пользователя. (Если я правильно помню, наблюдатель ActiveRecord, который это заменил, не работал в рейк-задачах или консоли.) О, этот код исходит из приложения Rails 2.3 - у меня есть несколько приложений Rails 3, но мне не нужен этот вид аудита для них еще.

У меня нет кода, который бы красиво отображал эту информацию (мы только копаем данные, когда нам нужно искать проблему), но поскольку изменения хранятся как JSON, это должно быть довольно просто.

Сначала мы сохраняем текущего пользователя в User.current, поэтому он доступен везде, поэтому в app/models/user.rb:

Class User < ActiveRecord::Base
  cattr_accessor :current
  ...
end

Текущий пользователь устанавливается в контроллере приложения для каждого такого запроса (и не вызывает проблем с concurrency):

def current_user
  User.current = session[:user_id] ? User.find_by_id(session[:user_id]) : nil
end

Вы можете установить User.current в своих рейк-задачах, если это имеет смысл.

Далее мы определяем модель для хранения информации аудита app/models/audit_log_entry.rb - вы хотите настроить IgnoreClassesRegEx для соответствия любым моделям, которые вы не хотите проверять:

# == Schema Information
#
# Table name: audit_log_entries
#
#  id         :integer         not null, primary key
#  class_name :string(255)
#  entity_id  :integer
#  user_id    :integer
#  action     :string(255)
#  data       :text
#  call_chain :text
#  created_at :datetime
#  updated_at :datetime
#

class AuditLogEntry < ActiveRecord::Base
  IgnoreClassesRegEx = /^ActiveRecord::Acts::Versioned|ActiveRecord.*::Session|Session|Sequence|SchemaMigration|CronRun|CronRunMessage|FontMetric$/
  belongs_to :user

  def entity (reload = false)
    @entity = nil if reload
    begin
      @entity ||= Kernel.const_get(class_name).find_by_id(entity_id)
    rescue
      nil
    end
  end

  def call_chain
    return if call_chain_before_type_cast.blank?
    if call_chain_before_type_cast.instance_of?(Array)
      call_chain_before_type_cast
    else
      JSON.parse(call_chain_before_type_cast)
    end
  end
  def data
    return if data_before_type_cast.blank?
    if data_before_type_cast.instance_of?(Hash)
      data_before_type_cast
    else
      JSON.parse(data_before_type_cast)
    end
  end

  def self.debug_entity(class_name, entity_id)
    require 'fastercsv'
    FasterCSV.generate do |csv|
      csv << %w[class_name entity_id date action first_name last_name data]
      find_all_by_class_name_and_entity_id(class_name, entity_id,
                                           :order => 'created_at').each do |a|
        csv << [a.class_name, a.entity_id, a.created_at, a.action, 
          (a.user && a.user.first_name), (a.user && a.user.last_name), a.data]
      end
    end
  end
end

Затем мы добавим некоторые методы в ActiveRecord::Base, чтобы сделать проверки. Вы хотите посмотреть на метод audit_log_clean_backtrace и изменить его для своих нужд. (FWIW, мы добавляем дополнения к существующим классам в lib/extensions/*.rb, которые загружаются в инициализатор.) В lib/extensions/active_record.rb:

class ActiveRecord::Base
  cattr_accessor :audit_log_backtrace_cleaner
  after_create  :audit_log_on_create
  before_update :save_audit_log_update_diff
  after_update  :audit_log_on_update
  after_destroy :audit_log_on_destroy
  def audit_log_on_create
    return if self.class.name =~ /AuditLogEntry/
    return if self.class.name =~ AuditLogEntry::IgnoreClassesRegEx
    audit_log_create 'CREATE', self, caller
  end
  def save_audit_log_update_diff
    @audit_log_update_diff = changes.reject{ |k,v| 'updated_at' == k }
  end
  def audit_log_on_update
    return if self.class.name =~ AuditLogEntry::IgnoreClassesRegEx
    return if @audit_log_update_diff.empty?
    audit_log_create 'UPDATE', @audit_log_update_diff, caller
  end
  def audit_log_on_destroy
    return if self.class.name =~ AuditLogEntry::IgnoreClassesRegEx
    audit_log_create 'DESTROY', self, caller
  end
  def audit_log_create (action, data, call_chain)
    AuditLogEntry.create :user       => User.current,
                         :action     => action,
                         :class_name => self.class.name,
                         :entity_id  => id,
                         :data       => data.to_json,
                         :call_chain => audit_log_clean_backtrace(call_chain).to_json
  end
  def audit_log_clean_backtrace (backtrace)
    if !ActiveRecord::Base.audit_log_backtrace_cleaner
      ActiveRecord::Base.audit_log_backtrace_cleaner = ActiveSupport::BacktraceCleaner.new
      ActiveRecord::Base.audit_log_backtrace_cleaner.add_silencer { |line| line =~ /\/lib\/rake\.rb/ }
      ActiveRecord::Base.audit_log_backtrace_cleaner.add_silencer { |line| line =~ /\/bin\/rake/ }
      ActiveRecord::Base.audit_log_backtrace_cleaner.add_silencer { |line| line =~ /\/lib\/(action_controller|active_(support|record)|hoptoad_notifier|phusion_passenger|rack|ruby|sass)\// }
      ActiveRecord::Base.audit_log_backtrace_cleaner.add_filter   { |line| line.gsub(RAILS_ROOT, '') }
    end
    ActiveRecord::Base.audit_log_backtrace_cleaner.clean backtrace
  end
end

Наконец, вот те тесты, которые мы имеем на этом - вам, конечно, нужно будет изменить фактические тестовые действия. test/integration/audit_log_test.rb

require File.dirname(__FILE__) + '/../test_helper'

class AuditLogTest < ActionController::IntegrationTest
  def setup
  end

  def test_audit_log
    u = users(:manager)
    log_in u
    a = Alert.first :order => 'id DESC'
    visit 'alerts/new'
    fill_in 'alert_note'
    click_button 'Send Alert'
    a = Alert.first :order => 'id DESC', :conditions => ['id > ?', a ? a.id : 0]
    ale = AuditLogEntry.first :conditions => {:class_name => 'Alert', :entity_id => a.id }
    assert_equal 'Alert',  ale.class_name
    assert_equal 'CREATE', ale.action
  end

private

  def log_in (user, password = 'test', initial_url = home_path)
    visit initial_url
    assert_contain 'I forgot my password'
    fill_in 'email',    :with => user.email
    fill_in 'password', :with => password
    click_button 'Log In'
  end

  def log_out
    visit logout_path
    assert_contain 'I forgot my password'
  end
end

И test/unit/audit_log_entry_test.rb:

# == Schema Information
#
# Table name: audit_log_entries
#
#  id         :integer         not null, primary key
#  class_name :string(255)
#  action     :string(255)
#  data       :text
#  user_id    :integer
#  created_at :datetime
#  updated_at :datetime
#  entity_id  :integer
#  call_chain :text
#

require File.dirname(__FILE__) + '/../test_helper'

class AuditLogEntryTest < ActiveSupport::TestCase
  test 'should handle create update and delete' do
    record = Alert.new :note => 'Test Alert'
    assert_difference 'Alert.count' do
      assert_difference 'AuditLogEntry.count' do
        record.save
        ale = AuditLogEntry.first :order => 'created_at DESC'
        assert ale
        assert_equal 'CREATE', ale.action, 'AuditLogEntry.action should be CREATE'
        assert_equal record.class.name, ale.class_name, 'AuditLogEntry.class_name should match record.class.name'
        assert_equal record.id, ale.entity_id, 'AuditLogEntry.entity_id should match record.id'
      end
    end
    assert_difference 'AuditLogEntry.count' do
      record.update_attribute 'note', 'Test Update'
      ale = AuditLogEntry.first :order => 'created_at DESC'
      expected_data = {'note' => ['Test Alert', 'Test Update']}
      assert ale
      assert_equal 'UPDATE', ale.action, 'AuditLogEntry.action should be UPDATE'
      assert_equal expected_data, ale.data
      assert_equal record.class.name, ale.class_name, 'AuditLogEntry.class_name should match record.class.name'
      assert_equal record.id, ale.entity_id, 'AuditLogEntry.entity_id should match record.id'
    end
    assert_difference 'AuditLogEntry.count' do
      record.destroy
      ale = AuditLogEntry.first :order => 'created_at DESC'
      assert ale
      assert_equal 'DESTROY', ale.action, 'AuditLogEntry.action should be CREATE'
      assert_equal record.class.name, ale.class_name, 'AuditLogEntry.class_name should match record.class.name'
      assert_equal record.id, ale.entity_id, 'AuditLogEntry.entity_id should match record.id'
      assert_nil Alert.find_by_id(record.id), 'Alert should be deleted'
    end
  end

  test 'should not log AuditLogEntry create entry and block on update and delete' do
    record = Alert.new :note => 'Test Alert'
    assert_difference 'Alert.count' do
      assert_difference 'AuditLogEntry.count' do
        record.save
      end
    end
    ale = AuditLogEntry.first :order => 'created_at DESC'
    assert_equal 'CREATE', ale.action, 'AuditLogEntry.action should be CREATE'
    assert_equal record.class.name, ale.class_name, 'AuditLogEntry.class_name should match record.class.name'
    assert_equal record.id, ale.entity_id, 'AuditLogEntry.entity_id should match record.id'
    assert_nil AuditLogEntry.first(:conditions => { :class_name => 'AuditLogEntry', :entity_id => ale.id })

    if ale.user_id.nil?
      u = User.first
    else
      u = User.first :conditions => ['id != ?', ale.user_id]
    end
    ale.user_id = u.id
    assert !ale.save

    assert !ale.destroy
  end
end

Ответ 4

https://github.com/collectiveidea/acts_as_audited

и

https://github.com/airblade/paper_trail

являются отличными решениями только для ActiveRecord, но поскольку большая часть ActiveRecord была извлечена до ActiveModel, вероятно, будет разумно расширить либо поддержку ActiveResource, по крайней мере, для чтения только для чтения поддержка. Я просматривал графические диаграммы Github и искажался по всему миру, и, похоже, не существует какой-либо постоянной разработки такого решения, тем не менее я ожидаю, что это будет проще реализовать поверх одного из этих двух плагинов, чем начинать с нуля. paper_trail, по-видимому, находится в более активной разработке и имеет некоторые коммиты для Rails 3.1, поэтому он может быть более актуальным с внутренними компонентами Rails и легче расширить, но это просто инстинкт кишки - я не знаком с внутренними либо.

Ответ 5

для отслеживания активности пользователя (CRUD), я создал класс, наследуемый от Logger, и теперь я планирую написать плагин litle для отслеживания пользователя, который я могу использовать для любого созданного приложения ROR. Я уже проверил, есть ли такой плагин, но я этого не видел. Я думаю, что есть много драгоценных камней, таких как бумажный след, act_as_audited или itslog, но я предпочитаю использовать плагин. Какие-либо предложения? Вот ссылка, которая может вам помочь: http://robaldred.co.uk/2009/01/custom-log-files-for-your-ruby-on-rails-applications/comment-page-1/#comment-342

хорошее кодирование

Ответ 6

Посмотрите на этот railscast, может быть, он может вам помочь: Notifications