Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto-register namespaces #83

Merged
merged 3 commits into from
Jun 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 25 additions & 3 deletions lib/oaken/seeds.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,38 @@ def self.defaults(**defaults) = attributes.merge!(**defaults)
def self.defaults_for(*keys) = attributes.slice(*keys)
def self.attributes = @attributes ||= {}.with_indifferent_access

def self.method_missing(name, ...)
if type = name.to_s.classify.safe_constantize
# Oaken's main auto-registering logic.
#
# So when you first call e.g. `accounts.create`, we'll hit `method_missing` here
# and automatically call `register Account`.
#
# We'll also match partial and full nested namespaces like in this order:
#
# accounts => Account
# account_jobs => AccountJob | Account::Job
# account_job_tasks => AccountJobTask | Account::JobTask | Account::Job::Task
#
# If you have classes that don't follow this naming convention, you must call `register` manually.
def self.method_missing(meth, ...)
name = meth.to_s.classify
name = name.sub!(/(?<=[a-z])(?=[A-Z])/, "::") until name.nil? or type = name.safe_constantize

if type
register type
public_send(name, ...)
public_send(meth, ...)
else
super
end
end
def self.respond_to_missing?(name, ...) = name.to_s.classify.safe_constantize || super

# Register a model class to be accessible as an instance method via `include Oaken::Seeds`.
# Note: Oaken's auto-register via `method_missing` means it's less likely you need to call this manually.
#
# register Account, Account::Job, Account::Job::Task
#
# Oaken uses the `table_name` of the passed classes for the method names, e.g. here they'd be
# `accounts`, `account_jobs`, and `account_job_tasks`, respectively.
def self.register(*types)
types.each do |type|
stored = provider.new(type) and define_method(stored.key) { stored }
Expand Down
3 changes: 3 additions & 0 deletions test/dummy/app/models/menu/item/detail.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class Menu::Item::Detail < ApplicationRecord
belongs_to :menu_item, class_name: "Menu::Item"
end
10 changes: 10 additions & 0 deletions test/dummy/db/migrate/20240630172609_create_menu_item_details.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class CreateMenuItemDetails < ActiveRecord::Migration[7.1]
def change
create_table :menu_item_details do |t|
t.references :menu_item, null: false, foreign_key: true
t.text :description, null: false

t.timestamps
end
end
end
11 changes: 10 additions & 1 deletion test/dummy/db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema[7.1].define(version: 2023_10_17_160024) do
ActiveRecord::Schema[7.1].define(version: 2024_06_30_172609) do
create_table "accounts", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
Expand All @@ -26,6 +26,14 @@
t.index ["user_id"], name: "index_administratorships_on_user_id"
end

create_table "menu_item_details", force: :cascade do |t|
t.integer "menu_item_id", null: false
t.text "description", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["menu_item_id"], name: "index_menu_item_details_on_menu_item_id"
end

create_table "menu_items", force: :cascade do |t|
t.integer "menu_id", null: false
t.string "name"
Expand Down Expand Up @@ -69,6 +77,7 @@

add_foreign_key "administratorships", "accounts"
add_foreign_key "administratorships", "users"
add_foreign_key "menu_item_details", "menu_items"
add_foreign_key "menu_items", "menus"
add_foreign_key "menus", "accounts"
add_foreign_key "orders", "menu_items", column: "item_id"
Expand Down
3 changes: 0 additions & 3 deletions test/dummy/db/seeds.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
Oaken.prepare do
defaults name: -> { "Shouldn't be used for users.name" }, title: -> { "Global Default Title" }

section :registrations
register Menu::Item

section :roots
user_counter, email_address_counter = 0, 0
users.defaults name: -> { "Customer #{user_counter += 1}" },
Expand Down
2 changes: 2 additions & 0 deletions test/dummy/db/seeds/accounts/kaspers_donuts.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
sprinkled_donut = menu_items.create menu: menu, name: "Sprinkled", price_cents: 10_10
menus.label basic: menu

menu_item_details.create :plain, menu_item: plain_donut, description: "Plain, but mighty."

section :orders
supporter = users.create name: "Super Supporter"
orders.insert_all [user_id: supporter.id, item_id: plain_donut.id] * 10
Expand Down
20 changes: 20 additions & 0 deletions test/dummy/test/models/oaken_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,26 @@ class OakenTest < ActiveSupport::TestCase
assert menus.basic
end

test "auto-registering with full namespaces" do
assert_respond_to self, :menu_items
assert_respond_to self, :menu_item_details

menu_item_details.plain.tap do |detail|
assert_equal "Plain", detail.menu_item.name
assert_equal "Plain, but mighty.", detail.description
assert_kind_of Menu::Item::Detail, detail
end
end

test "auto-registering with partial namespaces" do
Menu::HiddenDiscount = Class.new do
def self.table_name = "menu_hidden_discounts"
def self.column_names = []
end

assert_kind_of Oaken::Stored::ActiveRecord, Oaken::Seeds.menu_hidden_discounts
end

test "global attributes" do
plan = plans.upsert price_cents: 10_00

Expand Down