Skip to content

Commit

Permalink
More robust location definitions (#93)
Browse files Browse the repository at this point in the history
* More robust location definitions

Say you had this Oaken seed file:

```ruby
users.create :kasper, name: "Kasper"
```

We want `users.method(:kasper)` to point back to that file and line to help debugging.

However, we had a hardcoded db/seeds in our detection,
which meant that create/upsert/label in tests would fail on a NoMehodError because our `find` wouldn't return anything.

In tests, labels make less sense in general, so I'm thinking we should allow those.

I'm also thinking I should try to more sharply define the preparation phase versus the running phase.

The preparation phase: loading and executing db/seeds.rb, setup defaults, helpers and common records
The running phase: when running tests, you shouldn't mutate anything really, and labels shouldn't suddenly spring up, generally things should be deterministic.

`Oaken.preparing?`/`Oaken.running?` may be on the table too.
Note: this does get complicated if people are using `seed "cases/pagination"` with one-off seeds. Hm.

Anyway, I'm detecting the path via a quirk on the label + our loading so we don't need to use `lookup_paths` at all.
I'm not sure I'm liking any of this.

* Ruby 3.4 changed the format, hm

* Don't need to transform_values to avoid capturing the record reference I don't think
  • Loading branch information
kaspth authored Jan 9, 2025
1 parent d0312d9 commit 191e46b
Show file tree
Hide file tree
Showing 3 changed files with 19 additions and 5 deletions.
6 changes: 6 additions & 0 deletions lib/oaken.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ def load_onto(seeds) = @entries.each do |path|
seeds.class_eval path.read, path.to_s
end
end

def self.definition_location
# Trickery abounds! Due to Ruby's `caller_locations` + our `load_onto`'s `class_eval` above
# we can use this format to detect the location in the seed file where the call came from.
caller_locations(2, 8).find { _1.label.match? /block .*?load_onto/ }
end
end

def self.prepare(&block) = Seeds.instance_eval(&block)
Expand Down
12 changes: 7 additions & 5 deletions lib/oaken/stored/active_record.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,13 @@ def upsert(label = nil, unique_by: nil, **attributes)
#
# Note: `users.method(:someone).source_location` also points back to the file and line of the `label` call.
def label(**labels)
# TODO: Fix hardcoding of db/seeds instead of using Oaken.lookup_paths
location = caller_locations(1, 6).find { _1.path.match? /db\/seeds\// }
labels.each { |label, record| _label label, record.id }
end

private def _label(name, id)
raise ArgumentError, "you can only define labelled records outside of tests" \
unless location = Oaken::Loader.definition_location

labels.each do |label, record|
class_eval "def #{label} = find(#{record.id.inspect})", location.path, location.lineno
end
class_eval "def #{name} = find(#{id.inspect})", location.path, location.lineno
end
end
6 changes: 6 additions & 0 deletions test/dummy/test/models/oaken_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ def self.column_names = []
assert_match "db/seeds/test/data/users.rb", users.method(:test_user).source_location.first
end

test "can't use labels within tests" do
assert_raise ArgumentError do
users.label kasper_2: users.kasper
end
end

test "updating fixture" do
users.kasper.update name: "Kasper2"
assert_equal "Kasper2", users.kasper.name
Expand Down

0 comments on commit 191e46b

Please sign in to comment.