Skip to content

Commit

Permalink
Refactor table_name inference.
Browse files Browse the repository at this point in the history
Add specs for configuration (which affect table_name inference)
Update readme to inform on table_name convention and usage
  • Loading branch information
voltechs committed Dec 30, 2017
1 parent de4beea commit 836aad6
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 15 deletions.
43 changes: 38 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Compatible with ActiveRecord `4.0`, `4.1`, `4.2`, `5.0`, `5.1`

Confirmed production use in `4.2`

## Usage
## Installation

Add this line to your application's Gemfile:

Expand All @@ -26,29 +26,62 @@ Or install it yourself as:

$ gem install active_record-mti

### Application Code

In most cases, you shouldn't have to do anything beyond installing the gem. `ActiveRecord::MTI` will do it's best to determine the nature of inheritance in your models. If your models map to their own tables, `ActiveRecord::MTI` will step in and make sure inheritance is treated appropriately. Otherwise it will gracefully aquiece to `ActiveRecord`'s built-in `STI`.
## Usage

```ruby
class Account < ::ActiveRecord::Base
# table_name is 'accounts'
# ...
end

class User < Account
# table_name is 'account/users'
# ...
end

class Developer < Account
# table_name is 'account/developers'
# ...
end

class Admin < User
self.table_name = 'admins'
# ...
end

class Hacker < Developer
# table_name is 'account/developer/hackers'
# ...
end
```

In most cases, you shouldn't have to do anything beyond installing the gem. `ActiveRecord::MTI` will do it's best to determine the nature of inheritance in your models. If your models map to their own tables, `ActiveRecord::MTI` will step in and make sure inheritance is treated appropriately. Otherwise it will gracefully acquiesce to `ActiveRecord`'s built-in `STI`. _(see Table Names section below)_.

### Queries

`ActiveRecord` queries work as usual with the following differences:

- The default query of "\*" is changed to include the OID of each row for subclass discrimination. The default select will be `SELECT "accounts"."tableoid" AS tableoid, "accounts".*` (for example)

Note
### Table Names

Conventionally—to indicate the nature of inheritance—`ActiveRecord::MTI` expects the `table_name` of a child model to follow the `singular_parent_table_name/plural_child_table_name` pattern. As always, if you need to deviate from this, you can explicitly set the `table_name` as shown below, or configure `ActiveRecord::MTI` using the configure block.

Note, `ActiveRecord::MTI` will fall back on the unnested `table_name` if it's unable to find the nested form, and short of that, it will use the superclass's `table_name`.

### Configuration
`ActiveRecord::MTI` can be configured using a configure block.

```ruby
# config/initializers/active_record_mti.rb

ActiveRecord::MTI.configure do |config|
config.table_name_nesting = true
config.nesting_seperator = '/'
config.singular_parent = true
end
```

### Migrations

In your migrations define a table to inherit from another table:
Expand Down
66 changes: 56 additions & 10 deletions lib/active_record/mti/model_schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,72 @@ def self.prepended(base)
module ClassMethods
# Computes and returns a table name according to default conventions.
def compute_table_name
if self != base_class
# Nested classes are prefixed with singular parent table name.
if superclass < Base && !superclass.abstract_class?
contained = superclass.table_name
contained = contained.singularize if superclass.pluralize_table_names
contained += '/'
end
if not_base_model?

warned = false

potential_table_name = "#{full_table_name_prefix}#{contained}#{decorated_table_name(name)}#{full_table_name_suffix}"
table_name_parts = [
full_table_name_prefix,
nil, # Placeholder for contained_table_name (used later)
decorated_table_name(name),
full_table_name_suffix
]

if check_inheritance_of(potential_table_name)
potential_table_name
not_nested_table_name_candidate = table_name_parts.join

if ActiveRecord::MTI.configuration.table_name_nesting
table_name_parts[1] = contained_table_name if ActiveRecord::MTI.configuration.table_name_nesting
nested_table_name_candidate = table_name_parts.join
if check_inheritance_of(nested_table_name_candidate)
return nested_table_name_candidate
else
warned = true
ActiveRecord::MTI.logger.warn(<<-WARN.squish)
Couldn't find table definition '#{nested_table_name_candidate}' for #{name}.
WARN
end
end

if check_inheritance_of(not_nested_table_name_candidate)
if warned
ActiveRecord::MTI.logger.warn(<<-WARN.squish)
Found table definition '#{not_nested_table_name_candidate}' for #{name}.
Recommended explicitly setting table_name for this model if you're diviating from convention.
WARN
end
not_nested_table_name_candidate
else
if warned
ActiveRecord::MTI.logger.warn(<<-WARN.squish)
Falling back on superclass (#{superclass.name}) table definition '#{superclass.table_name}' for #{name}.
Recommended explicitly setting table_name for this model if you're diviating from convention.
WARN
end
superclass.table_name
end
else
super
end
end

def not_base_model?
self != base_class &&
superclass < Base &&
!superclass.abstract_class?
end

def contained_table_name
contained_parent_table_name + ActiveRecord::MTI.configuration.nesting_seperator
end

def contained_parent_table_name
if ActiveRecord::MTI.configuration.singular_parent
superclass.table_name.singularize
else
superclass.table_name
end
end

def full_table_name_suffix #:nodoc:
super
rescue NoMethodError
Expand Down
33 changes: 33 additions & 0 deletions spec/active_record/config_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
require 'spec_helper'

describe 'ActiveRecord::MTI Config' do

context 'can configure' do
after(:each) do
ActiveRecord::MTI.reset_configuration
Manager.reset_table_name
end
{
table_name_nesting: {
setting: false,
expectation: 'managers'
},
nesting_seperator: {
setting: '_',
expectation: 'user_managers'
},
singular_parent: {
setting: false,
expectation: 'users/managers'
}
}.each do |setting, meta|
it "##{setting}" do
ActiveRecord::MTI.configure do |config|
config.send("#{setting}=", meta[:setting])
end
expect(Manager.reset_table_name).to eq(meta[:expectation])
end
end
end

end

0 comments on commit 836aad6

Please sign in to comment.