All Projects → jonmagic → arca

jonmagic / arca

Licence: other
Arca is a callback analyzer for ActiveRecord ideally suited for digging yourself out of callback hell

Programming Languages

ruby
36898 projects - #4 most used programming language
Roff
2310 projects

Projects that are alternatives of or similar to arca

computed model
Batch loader with dependency resolution and computed fields
Stars: ✭ 22 (-4.35%)
Mutual labels:  activerecord
ar-check
Enable PostgreSQL's CHECK constraints on ActiveRecord migrations
Stars: ✭ 17 (-26.09%)
Mutual labels:  activerecord
wharel
Arel made simple
Stars: ✭ 95 (+313.04%)
Mutual labels:  activerecord
wisper-activerecord
Transparently publish all model changes to subscribers
Stars: ✭ 97 (+321.74%)
Mutual labels:  activerecord
datetime-scopes
A plugin for ActiveRecord which adds very helpful methods for querying records over time ranges (weeks/days/months/years), and it also respects time-zones!
Stars: ✭ 14 (-39.13%)
Mutual labels:  activerecord
LocalSupport
A directory of local support services and volunteer opportunities
Stars: ✭ 60 (+160.87%)
Mutual labels:  activerecord
models stats
Charts for your rails models with MetricsGraphics.js and NVD3
Stars: ✭ 43 (+86.96%)
Mutual labels:  activerecord
array enum
String to integer mapping for PostgreSQL array columns
Stars: ✭ 28 (+21.74%)
Mutual labels:  activerecord
filtered
Filters ActiveRecord queries in a nice way
Stars: ✭ 28 (+21.74%)
Mutual labels:  activerecord
normalize attributes
Sometimes you want to normalize data before saving it to the database like down casing e-mails, removing spaces and so on. This Rails plugin allows you to do so in a simple way.
Stars: ✭ 41 (+78.26%)
Mutual labels:  activerecord
filedb
ActiveRecord for static data definitions based on files
Stars: ✭ 72 (+213.04%)
Mutual labels:  activerecord
active-persistence
Active Persistence is a implementation of Active Record Query Interface for JPA that makes it easy and fun.
Stars: ✭ 14 (-39.13%)
Mutual labels:  activerecord
active model serializers validator
🃏 An extension to ActiveModel::Serializer that validates serializers output against a JSON schema
Stars: ✭ 18 (-21.74%)
Mutual labels:  activerecord
rails-countries
Integration between Rails and countries gem.
Stars: ✭ 17 (-26.09%)
Mutual labels:  activerecord
timeliness-i18n
Translations for timeliness and validates_timeliness gem.
Stars: ✭ 16 (-30.43%)
Mutual labels:  activerecord
wp-activerecord
An ActiveRecord implementation for WordPress
Stars: ✭ 19 (-17.39%)
Mutual labels:  activerecord
idy
👓 An ID obfuscator for ActiveRecord
Stars: ✭ 15 (-34.78%)
Mutual labels:  activerecord
the schema is
ActiveRecord schema annotations done right
Stars: ✭ 44 (+91.3%)
Mutual labels:  activerecord
ar-ondemand
Fast access to database results without the memory overhead of ActiveRecord objects
Stars: ✭ 37 (+60.87%)
Mutual labels:  activerecord
rails-microservices-book
A guide to building distributed Ruby on Rails applications using Protocol Buffers, NATS and RabbitMQ
Stars: ✭ 23 (+0%)
Mutual labels:  activerecord

ActiveRecord Callback Analyzer

Arca is a callback analyzer for ActiveRecord models ideally suited for digging yourself out of callback hell. At best it will help you move towards a more maintainable design and at worst it can be used in your test suite to give you feedback when callbacks change.

Arca helps you answer questions like:

  • how spread out callbacks are for each model
  • how many callbacks use conditionals (:if, :unless, and :on)
  • how many possible permutations exist per callback type (:commit, :create, :destroy, :find, :initialize, :rollback, :save, :touch, :update, :validation) taking conditionals into consideration

The Arca library has two main components, the collector and the reporter. Include the collector module in ActiveRecord::Base before your models are loaded.

At GitHub, we test callbacks by whitelisting existing callbacks, and adding a lint test to ensure new callbacks are not added without review. The examples folder is a good starting point.

Requirements

travis-ci build status

Arca is tested against ActiveRecord 3.2 and 4.2 running on Ruby 1.9.3, 2.0.0, 2.1.0, and 2.2.0.

Usage

Add the gem to your Gemfile and run bundle.

gem 'arca'

In your test helper (test/test_helper.rb for example) require the Arca library and include the Arca::Collector in ActiveRecord::Base.

require "active_record"
require "arca"

class ActiveRecord::Base
  include Arca::Collector
end

# load your app. It's important to setup before loading your models because Arca
# works by wrapping itself around the callback method definitions (before_save,
# after_save, etc) and then records how and where those methods are used.

In this example the Annoucements module is included in Ticket and defines it's own callback.

class Ticket < ActiveRecord::Base
  include Announcements

  before_save :set_title, :set_body
  before_save :upcase_title, :if => :title_is_a_shout?

  def set_title
    self.title ||= "Ticket id #{SecureRandom.hex(2)}"
  end

  def set_body
    self.body ||= "Everything is broken."
  end

  def upcase_title
    self.title = title.upcase
  end

  def title_is_a_shout?
    self.title.split(" ").size == 1
  end
end
module Announcements
  def self.included(base)
    base.class_eval do
      after_save :announce_save
    end
  end

  def announce_save
    puts "saved #{self.class.name.downcase}!"
  end
end

Use Arca[Ticket].report to analyze the callbacks for the Ticket class.

> Arca[Ticket].report
{
                   :model_name => "Ticket",
              :model_file_path => "test/fixtures/ticket.rb",
              :callbacks_count => 4,
           :conditionals_count => 1,
          :lines_between_count => 6,
     :external_callbacks_count => 1,
       :external_targets_count => 0,
  :external_conditionals_count => 0,
      :calculated_permutations => 2
}

Try out Arca[Ticket].analyzed_callbacks to see where and how each callback works and the order they run in.

> Arca[Ticket].analyzed_callbacks
{
  :before_save => [
    {
      :callback                       => :before_save,
      :callback_file_path             => "test/fixtures/ticket.rb",
      :callback_line_number           => 5,
      :external_callback              => false,
      :target                         => :set_title,
      :target_file_path               => "test/fixtures/ticket.rb",
      :target_line_number             => 8,
      :external_target                => false,
      :lines_to_target                => 3,
      :conditional                    => nil,
      :conditional_target             => nil,
      :conditional_target_file_path   => nil,
      :conditional_target_line_number => nil,
      :external_conditional_target    => nil,
      :lines_to_conditional_target    => nil
    },
    {
      :callback                       => :before_save,
      :callback_file_path             => "test/fixtures/ticket.rb",
      :callback_line_number           => 5,
      :external_callback              => false,
      :target                         => :set_body,
      :target_file_path               => "test/fixtures/ticket.rb",
      :target_line_number             => 12,
      :external_target                => false,
      :lines_to_target                => 7,
      :conditional                    => nil,
      :conditional_target             => nil,
      :conditional_target_file_path   => nil,
      :conditional_target_line_number => nil,
      :external_conditional_target    => nil,
      :lines_to_conditional_target    => nil
    },
    {
      :callback                       => :before_save,
      :callback_file_path             => "test/fixtures/ticket.rb",
      :callback_line_number           => 6,
      :external_callback              => false,
      :target                         => :upcase_title,
      :target_file_path               => "test/fixtures/ticket.rb",
      :target_line_number             => 16,
      :external_target                => false,
      :lines_to_target                => 10,
      :conditional                    => :if,
      :conditional_target             => :title_is_a_shout?,
      :conditional_target_file_path   => "test/fixtures/ticket.rb",
      :conditional_target_line_number => 20,
      :external_conditional_target    => false,
      :lines_to_conditional_target    => nil
    }
  ],
  :after_save  => [
    {
      :callback                       => :after_save,
      :callback_file_path             => "test/fixtures/announcements.rb",
      :callback_line_number           => 4,
      :external_callback              => true,
      :target                         => :announce_save,
      :target_file_path               => "test/fixtures/announcements.rb",
      :target_line_number             => 8,
      :external_target                => false,
      :lines_to_target                => 4,
      :conditional                    => nil,
      :conditional_target             => nil,
      :conditional_target_file_path   => nil,
      :conditional_target_line_number => nil,
      :external_conditional_target    => nil,
      :lines_to_conditional_target    => nil
    }
  ]
}

I'm working on a project at GitHub that feels pain when callback behavior changes so I decided to build this tool to help us manage change better and hopefully in the long run move away from ActiveRecord callbacks for most things.

For the first iteration I am hoping to use this tool in a set of model lint tests that break when callback behavior changes.

  def assert_equal(expected, actual)
    super(expected, actual, ARCA_FAILURE_MESSAGE)
  end

  def test_foo
    report = Arca[Foo].report
    expected = {
      :model_name                  => "Foo",
      :model_file_path             => "app/models/foo.rb",
      :callbacks_count             => 30,
      :conditionals_count          => 3,
      :lines_between_count         => 1026,
      :external_callbacks_count    => 12,
      :external_targets_count      => 3,
      :external_conditionals_count => 2,
      :calculated_permutations     => 11
    }

    assert_equal expected, report.to_hash
  end

When change happens and that test fails it outputs a helpful error message.

---------------------------------------------
Please /cc @github/migration on the PR if you
have to update this test to make it pass.
---------------------------------------------

License

The MIT License (MIT)

Copyright (c) 2015 Jonathan Hoyt

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].