[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fpboling%2Fanonymous_active_record.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fpboling%2Fanonymous_active_record?ref=badge_shield)
[![Version](https://img.shields.io/gem/v/anonymous_active_record.svg)](https://rubygems.org/gems/anonymous_active_record)
[![Downloads Today](https://img.shields.io/gem/rd/anonymous_active_record.svg)](https://github.com/pboling/anonymous_active_record)
[![Depfu](https://badges.depfu.com/badges/272ce0df3bc6df5cbea9354e2c3b65af/overview.svg)](https://depfu.com/github/pboling/anonymous_active_record?project_id=5614)
[![CodeCov][🖇codecov-img♻️]][🖇codecov]
[![Test Coverage](https://api.codeclimate.com/v1/badges/fe504d4ab2fb77cecf7d/test_coverage)](https://codeclimate.com/github/pboling/anonymous_active_record/test_coverage)
[![Maintainability](https://api.codeclimate.com/v1/badges/fe504d4ab2fb77cecf7d/maintainability)](https://codeclimate.com/github/pboling/anonymous_active_record/maintainability)
[![CI Supported Build][🚎s-wfi]][🚎s-wf]
[![CI Unsupported Build][🚎us-wfi]][🚎us-wf]
[![CI Style Build][🚎st-wfi]][🚎st-wf]
[![CI Coverage Build][🚎cov-wfi]][🚎cov-wf]
[![CI Heads Build][🚎hd-wfi]][🚎hd-wf]
[![CI Ancient Build][🚎an-wfi]][🚎an-wf]
[![CI Dead Build][🚎ded-wfi]][🚎ded-wf]
[🖇codecov-img♻️]: https://codecov.io/gh/pboling/anonymous_active_record/graph/badge.svg?token=FLAk5BEAkv
[🖇codecov]: https://codecov.io/gh/pboling/anonymous_active_record
[🚎s-wf]: https://github.com/pboling/anonymous_active_record/actions/workflows/supported.yml
[🚎s-wfi]: https://github.com/pboling/anonymous_active_record/actions/workflows/supported.yml/badge.svg
[🚎us-wf]: https://github.com/pboling/anonymous_active_record/actions/workflows/unsupported.yml
[🚎us-wfi]: https://github.com/pboling/anonymous_active_record/actions/workflows/unsupported.yml/badge.svg
[🚎st-wf]: https://github.com/pboling/anonymous_active_record/actions/workflows/style.yml
[🚎st-wfi]: https://github.com/pboling/anonymous_active_record/actions/workflows/style.yml/badge.svg
[🚎cov-wf]: https://github.com/pboling/anonymous_active_record/actions/workflows/coverage.yml
[🚎cov-wfi]: https://github.com/pboling/anonymous_active_record/actions/workflows/coverage.yml/badge.svg
[🚎hd-wf]: https://github.com/pboling/anonymous_active_record/actions/workflows/heads.yml
[🚎hd-wfi]: https://github.com/pboling/anonymous_active_record/actions/workflows/heads.yml/badge.svg
[🚎an-wf]: https://github.com/pboling/anonymous_active_record/actions/workflows/ancient.yml
[🚎an-wfi]: https://github.com/pboling/anonymous_active_record/actions/workflows/ancient.yml/badge.svg
[🚎ded-wf]: https://github.com/pboling/anonymous_active_record/actions/workflows/dead.yml
[🚎ded-wfi]: https://github.com/pboling/anonymous_active_record/actions/workflows/dead.yml/badge.svg
-----
[![Liberapay Patrons][⛳liberapay-img]][⛳liberapay]
[![Sponsor Me on Github][🖇sponsor-img]][🖇sponsor]
[![Polar Shield][🖇polar-img]][🖇polar]
[![Donate to my FLOSS or refugee efforts at ko-fi.com][🖇kofi-img]][🖇kofi]
[![Donate to my FLOSS or refugee efforts using Patreon][🖇patreon-img]][🖇patreon]
[⛳liberapay-img]: https://img.shields.io/liberapay/patrons/pboling.svg?logo=liberapay
[⛳liberapay]: https://liberapay.com/pboling/donate
[🖇sponsor-img]: https://img.shields.io/badge/Sponsor_Me!-pboling.svg?style=social&logo=github
[🖇sponsor]: https://github.com/sponsors/pboling
[🖇polar-img]: https://polar.sh/embed/seeks-funding-shield.svg?org=pboling
[🖇polar]: https://polar.sh/pboling
[🖇kofi-img]: https://img.shields.io/badge/buy%20me%20coffee-donate-yellow.svg
[🖇kofi]: https://ko-fi.com/O5O86SNP4
[🖇patreon-img]: https://img.shields.io/badge/patreon-donate-yellow.svg
[🖇patreon]: https://patreon.com/galtzo
This library was 🎩 inspired by 🎩, the [Wolverine project](https://github.com/mcary/wolverine), which [implemented a clever workaround](https://github.com/mcary/wolverine/commit/fa27fa2cc485b2fa83d71b2045ba5a0a069dba75) to the official non-support of [anonymous classes by ActiveRecord](https://github.com/rails/rails/issues/8934).
Warning: Use of this gem is a **security risk**, due to the use of Ruby's `eval`. It is intended for use in a test suite, or other non-critical environment.
| Project | AnonymousActiveRecord |
|----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| gem name | [anonymous_active_record](https://rubygems.org/gems/anonymous_active_record) |
| documentation | [on Github.com][homepage], [on RDoc.info][documentation], [![on Railsbling.com][🚎blog-img]][🚎blog] |
| code triage | [![Open Source Helpers](https://www.codetriage.com/pboling/anonymous_active_record/badges/users.svg)](https://www.codetriage.com/pboling/anonymous_active_record) |
| expert support | [![Get help on Codementor](https://cdn.codementor.io/badges/get_help_github.svg)](https://www.codementor.io/peterboling?utm_source=github&utm_medium=button&utm_term=peterboling&utm_campaign=github) |
| `...` 💖 | [![Liberapay Patrons][⛳liberapay-img]][⛳liberapay] [![Sponsor Me][🖇sponsor-img]][🖇sponsor] [![Follow Me on LinkedIn][🖇linkedin-img]][🖇linkedin] [![Find Me on WellFound:][✌️wellfound-img]][✌️wellfound] [![Find Me on CrunchBase][💲crunchbase-img]][💲crunchbase] [![My LinkTree][🌳linktree-img]][🌳linktree] [![Follow Me on Ruby.Social][🐘ruby-mast-img]][🐘ruby-mast] [![Tweet @ Peter][🐦tweet-img]][🐦tweet] [💻][coderme] [🌏][aboutme] |
[🐦tweet-img]: https://img.shields.io/twitter/follow/galtzo.svg?style=social&label=Follow%20%40galtzo
[🐦tweet]: http://twitter.com/galtzo
[🚎blog]: http://www.railsbling.com/tags/anonymous_active_record/
[🚎blog-img]: https://img.shields.io/badge/blog-railsbling-brightgreen.svg?style=flat
[🖇linkedin]: http://www.linkedin.com/in/peterboling
[🖇linkedin-img]: https://img.shields.io/badge/PeterBoling-blue?style=plastic&logo=linkedin
[✌️wellfound]: https://angel.co/u/peter-boling
[✌️wellfound-img]: https://img.shields.io/badge/peter--boling-orange?style=plastic&logo=wellfound
[💲crunchbase]: https://www.crunchbase.com/person/peter-boling
[💲crunchbase-img]: https://img.shields.io/badge/peter--boling-purple?style=plastic&logo=crunchbase
[🐘ruby-mast]: https://ruby.social/@galtzo
[🐘ruby-mast-img]: https://img.shields.io/mastodon/follow/109447111526622197?domain=https%3A%2F%2Fruby.social&style=plastic&logo=mastodon&label=Ruby%20%40galtzo
[🌳linktree]: https://linktr.ee/galtzo
[🌳linktree-img]: https://img.shields.io/badge/galtzo-purple?style=plastic&logo=linktree
[aboutme]: https://about.me/peter.boling
[coderme]: https://coderwall.com/Peter%20Boling
## Installation
Install the gem and add to the application's Gemfile by executing,
adding the `--group test` on the end if you will only use it for testing
(which is the only way it should be used):
$ bundle add anonymous_active_record --group test
If bundler is not being used to manage dependencies, install the gem by executing:
$ gem install anonymous_active_record
## Compatibility
This gem is compatible with, as of Sep 2024:
• Ruby 2.4, 2.5, 2.6, 2.7, 3.0, 3.1, 3.2, 3.3, ruby-head, truffleruby-head
## Usage
Require the library in your `spec_helper` or other test suite boot file.
```ruby
require "anonymous_active_record"
```
Let's say you want to write specs for a module, `HasBalloon`, which provides a method `has_balloon?`, and will be mixed into ActiveRecord classes.
```ruby
module HasBalloon
def has_balloon?
name == "Spot" # only Spot has a balloon
end
end
```
This won't work [(really!)](https://github.com/rails/rails/issues/8934):
```ruby
let(:ar_with_balloon) do
Class.new(ActiveRecord::Base) do
attr_accessor :name
include HasBalloon
def flowery_name
"#{b_f}#{name}#{b_f}"
end
def b_f
has_balloon? ? "🎈" : "🌸"
end
end
end
```
So do this instead:
```ruby
let(:ar_with_balloon) do
AnonymousActiveRecord.generate(columns: ["name"]) do
include HasBalloon
def flowery_name
"#{b_f}#{name}#{b_f}"
end
def b_f
has_balloon? ? "🎈" : "🌸"
end
end
end
it "can test the module" do
expect(ar_with_balloon.new(name: "Spot").flowery_name).to(eq("🎈Spot🎈"))
expect(ar_with_balloon.new(name: "Not Spot").flowery_name).to(eq("🌸Not Spot🌸"))
end
```
### Generate Options
```ruby
AnonymousActiveRecord.generate(
table_name: "a_table_name",
# if table_name is not set klass_basename will be used to derive a unique random table_name
# default is a unique random table name
klass_basename: "anons", # is default
columns: ["name"],
# columns default is [],
# meaning class will have ['id', 'created_at', 'updated_at'], as the AR defaults
# Optionally provide an array of hashes and thereby designate column type:
# [{name: 'name', type: 'string'}, {name: 'baked_at', type: 'time'}]
timestamps: true, # is default
indexes: [{columns: ["name"], unique: true}],
# indexes default is [],
# meaning class will have no indexes, as the AR defaults
# Optionally provide an array of hashes of index options (similar to those used in Rails migrations):
# [{columns: ['name'], unique: true}, {columns: ['baked_at']}]
connection_params: {adapter: "sqlite3", encoding: "utf8", database: ":memory:"}, # is default
) do
# code which becomes part of the class definition
end
```
The block is optional.
### Factory Options
```ruby
AnonymousActiveRecord.factory(
source_data: [{name: "Phil"}, {name: "Vickie"}],
# Array of hashes, where each hash represents a record that will be created
# ... The rest of the options are the same as for generate, see above.
) do
# same as for generate, see above.
end
```
The block is optional.
There is also a `factory!` method that will raise if the create fails, accomplished by calling `create!` instead of `create`.
## 🤝 Contributing
See [CONTRIBUTING.md][🤝contributing]
[🤝contributing]: CONTRIBUTING.md
### Code Coverage
If you need some ideas of where to help, you could work on adding more code coverage.
[![Coverage Graph][🔑codecov-g]][🖇codecov]
[🔑codecov-g]: https://codecov.io/gh/pboling/anonymous_active_record/graphs/tree.svg?token=FLAk5BEAkv
## 🌈 Contributors
[![Contributors][🖐contributors-img]][🖐contributors]
Made with [contributors-img][🖐contrib-rocks].
[🖐contrib-rocks]: https://contrib.rocks
[🖐contributors]: https://github.com/pboling/anonymous_active_record/graphs/contributors
[🖐contributors-img]: https://contrib.rocks/image?repo=pboling/anonymous_active_record
## Star History