familia 2.0.0.pre6 → 2.0.0.pre7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/claude-code-review.yml +57 -0
- data/.github/workflows/claude.yml +71 -0
- data/.gitignore +5 -1
- data/.rubocop.yml +3 -0
- data/CLAUDE.md +32 -13
- data/Gemfile +2 -2
- data/Gemfile.lock +2 -2
- data/docs/wiki/Feature-System-Guide.md +36 -5
- data/docs/wiki/Home.md +30 -20
- data/docs/wiki/Relationships-Guide.md +684 -0
- data/examples/bit_encoding_integration.rb +237 -0
- data/examples/redis_command_validation_example.rb +231 -0
- data/examples/relationships_basic.rb +273 -0
- data/lib/familia/connection.rb +3 -3
- data/lib/familia/data_type.rb +7 -4
- data/lib/familia/features/encrypted_fields/concealed_string.rb +21 -23
- data/lib/familia/features/encrypted_fields.rb +413 -4
- data/lib/familia/features/expiration.rb +319 -33
- data/lib/familia/features/quantization.rb +385 -44
- data/lib/familia/features/relationships/cascading.rb +438 -0
- data/lib/familia/features/relationships/indexing.rb +370 -0
- data/lib/familia/features/relationships/membership.rb +503 -0
- data/lib/familia/features/relationships/permission_management.rb +264 -0
- data/lib/familia/features/relationships/querying.rb +620 -0
- data/lib/familia/features/relationships/redis_operations.rb +274 -0
- data/lib/familia/features/relationships/score_encoding.rb +442 -0
- data/lib/familia/features/relationships/tracking.rb +379 -0
- data/lib/familia/features/relationships.rb +466 -0
- data/lib/familia/features/transient_fields.rb +192 -10
- data/lib/familia/features.rb +2 -1
- data/lib/familia/horreum/subclass/definition.rb +1 -1
- data/lib/familia/validation/command_recorder.rb +336 -0
- data/lib/familia/validation/expectations.rb +519 -0
- data/lib/familia/validation/test_helpers.rb +443 -0
- data/lib/familia/validation/validator.rb +412 -0
- data/lib/familia/validation.rb +140 -0
- data/lib/familia/version.rb +1 -1
- data/try/edge_cases/hash_symbolization_try.rb +1 -0
- data/try/edge_cases/reserved_keywords_try.rb +1 -0
- data/try/edge_cases/string_coercion_try.rb +2 -0
- data/try/encryption/encryption_core_try.rb +3 -1
- data/try/features/categorical_permissions_try.rb +515 -0
- data/try/features/encryption_fields/concealed_string_core_try.rb +3 -0
- data/try/features/encryption_fields/context_isolation_try.rb +1 -0
- data/try/features/relationships_edge_cases_try.rb +145 -0
- data/try/features/relationships_performance_minimal_try.rb +132 -0
- data/try/features/relationships_performance_simple_try.rb +155 -0
- data/try/features/relationships_performance_try.rb +420 -0
- data/try/features/relationships_performance_working_try.rb +144 -0
- data/try/features/relationships_try.rb +237 -0
- data/try/features/safe_dump_try.rb +3 -0
- data/try/features/transient_fields/redacted_string_try.rb +2 -0
- data/try/features/transient_fields/single_use_redacted_string_try.rb +2 -0
- data/try/helpers/test_helpers.rb +1 -1
- data/try/horreum/base_try.rb +14 -8
- data/try/horreum/enhanced_conflict_handling_try.rb +2 -0
- data/try/horreum/relations_try.rb +1 -1
- data/try/validation/atomic_operations_try.rb.disabled +320 -0
- data/try/validation/command_validation_try.rb.disabled +207 -0
- data/try/validation/performance_validation_try.rb.disabled +324 -0
- data/try/validation/real_world_scenarios_try.rb.disabled +390 -0
- metadata +32 -4
- data/docs/wiki/RelatableObjects-Guide.md +0 -563
- data/lib/familia/features/relatable_objects.rb +0 -125
- data/try/features/relatable_objects_try.rb +0 -220
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7a93de51c8a35c420067d9e48895af37862866667af224a8e13f44647c4e4c76
|
4
|
+
data.tar.gz: bbdf64f77c182a0498fc757c393071b04b5da3594b4c7d5c12992fe058b44777
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 59a39a481db42739bbebabab9211645b2f7e9eb605742ab936c0b65dfc32973931b1b1f8a1c5d725fed5f5eb0ebdbb87c1c405f87108390ad3b6eb8f5ca89c5a
|
7
|
+
data.tar.gz: 4dc7dededb21bd7ee988a4d260e30eceb43f19be4c3f5d99f87bac5d632af6e517af5eab2633758e0c6b1552a2635bd0e024ace2210ee1ee83362aa811458b8c
|
@@ -0,0 +1,57 @@
|
|
1
|
+
name: Claude Code Review
|
2
|
+
|
3
|
+
on:
|
4
|
+
pull_request:
|
5
|
+
types: [opened, synchronize]
|
6
|
+
# Optional: Only run on specific file changes
|
7
|
+
# paths:
|
8
|
+
# - "src/**/*.ts"
|
9
|
+
# - "src/**/*.tsx"
|
10
|
+
# - "src/**/*.js"
|
11
|
+
# - "src/**/*.jsx"
|
12
|
+
|
13
|
+
jobs:
|
14
|
+
claude-review:
|
15
|
+
# Optional: Filter by PR author
|
16
|
+
# if: |
|
17
|
+
# github.event.pull_request.user.login == 'external-contributor' ||
|
18
|
+
# github.event.pull_request.user.login == 'new-developer' ||
|
19
|
+
# github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'
|
20
|
+
|
21
|
+
runs-on: ubuntu-latest
|
22
|
+
permissions:
|
23
|
+
contents: read
|
24
|
+
pull-requests: read
|
25
|
+
issues: read
|
26
|
+
id-token: write
|
27
|
+
|
28
|
+
steps:
|
29
|
+
- name: Checkout repository
|
30
|
+
uses: actions/checkout@v4
|
31
|
+
with:
|
32
|
+
fetch-depth: 1
|
33
|
+
|
34
|
+
- name: Run Claude Code Review
|
35
|
+
id: claude-review
|
36
|
+
uses: anthropics/claude-code-action@beta
|
37
|
+
with:
|
38
|
+
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
39
|
+
|
40
|
+
# Optional: Use sticky comments to make Claude reuse the same comment on subsequent pushes to the same PR
|
41
|
+
use_sticky_comment: true
|
42
|
+
|
43
|
+
prompt: |
|
44
|
+
Please review this pull request and provide feedback on:
|
45
|
+
- Code quality and best practices
|
46
|
+
- Potential bugs or issues
|
47
|
+
- Performance considerations
|
48
|
+
- Security concerns
|
49
|
+
- Test coverage
|
50
|
+
|
51
|
+
Use the repository's CLAUDE.md for guidance on style and conventions. Be constructive and helpful in your feedback.
|
52
|
+
|
53
|
+
Use `gh pr comment` with your Bash tool to leave your review as a comment on the PR.
|
54
|
+
|
55
|
+
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
|
56
|
+
# or https://docs.anthropic.com/en/docs/claude-code/sdk#command-line for available options
|
57
|
+
claude_args: '--allowed-tools "Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"'
|
@@ -0,0 +1,71 @@
|
|
1
|
+
name: Claude Code
|
2
|
+
|
3
|
+
on:
|
4
|
+
issue_comment:
|
5
|
+
types: [created]
|
6
|
+
pull_request_review_comment:
|
7
|
+
types: [created]
|
8
|
+
issues:
|
9
|
+
types: [opened, assigned]
|
10
|
+
pull_request_review:
|
11
|
+
types: [submitted]
|
12
|
+
|
13
|
+
jobs:
|
14
|
+
claude:
|
15
|
+
if: |
|
16
|
+
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
|
17
|
+
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
|
18
|
+
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
|
19
|
+
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
|
20
|
+
runs-on: ubuntu-latest
|
21
|
+
permissions:
|
22
|
+
contents: read
|
23
|
+
pull-requests: read
|
24
|
+
issues: read
|
25
|
+
id-token: write
|
26
|
+
actions: read # Required for Claude to read CI results on PRs
|
27
|
+
steps:
|
28
|
+
- name: Checkout repository
|
29
|
+
uses: actions/checkout@v4
|
30
|
+
with:
|
31
|
+
fetch-depth: 1
|
32
|
+
|
33
|
+
- name: Run Claude Code
|
34
|
+
id: claude
|
35
|
+
uses: anthropics/claude-code-action@v1
|
36
|
+
with:
|
37
|
+
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
38
|
+
|
39
|
+
# This is an optional setting that allows Claude to read CI results on PRs
|
40
|
+
additional_permissions: |
|
41
|
+
actions: read
|
42
|
+
|
43
|
+
# Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4)
|
44
|
+
# model: "claude-opus-4-20250514"
|
45
|
+
|
46
|
+
# Optional: Customize the trigger phrase (default: @claude)
|
47
|
+
# trigger_phrase: "/claude"
|
48
|
+
|
49
|
+
# Optional: Trigger when specific user is assigned to an issue
|
50
|
+
# assignee_trigger: "claude-bot"
|
51
|
+
|
52
|
+
# Optional: Allow Claude to run specific commands
|
53
|
+
# allowed_tools: "Bash(npm install),Bash(npm run build),Bash(npm run test:*),Bash(npm run lint:*)"
|
54
|
+
|
55
|
+
# Optional: Add custom instructions for Claude to customize its behavior for your project
|
56
|
+
# custom_instructions: |
|
57
|
+
# Follow our coding standards
|
58
|
+
# Ensure all new code has tests
|
59
|
+
# Use TypeScript for new files
|
60
|
+
|
61
|
+
# Optional: Custom environment variables for Claude
|
62
|
+
# claude_env: |
|
63
|
+
# NODE_ENV: test
|
64
|
+
|
65
|
+
# Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it.
|
66
|
+
# prompt: 'Update the pull request description to include a summary of changes.'
|
67
|
+
|
68
|
+
# Optional: Add claude_args to customize behavior and configuration
|
69
|
+
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
|
70
|
+
# or https://docs.anthropic.com/en/docs/claude-code/sdk#command-line for available options
|
71
|
+
# claude_args: '--model claude-opus-4-1-20250805 --allowed-tools Bash(gh pr:*)'
|
data/.gitignore
CHANGED
@@ -1,14 +1,19 @@
|
|
1
1
|
.DS_Store
|
2
2
|
.bundle
|
3
3
|
.byebug*
|
4
|
+
.ruby-lsp
|
4
5
|
.prompts
|
5
6
|
.history
|
6
7
|
.devcontainer
|
7
8
|
.vscode
|
9
|
+
.serena
|
10
|
+
.claude
|
11
|
+
.yardoc
|
8
12
|
.*.json
|
9
13
|
*.env
|
10
14
|
*.log
|
11
15
|
*.md
|
16
|
+
.mcp.json
|
12
17
|
!README.md
|
13
18
|
!CLAUDE.md
|
14
19
|
*.txt
|
@@ -21,7 +26,6 @@ tmp
|
|
21
26
|
vendor
|
22
27
|
*.gem
|
23
28
|
doc/
|
24
|
-
.yardoc
|
25
29
|
!docs/overview.md
|
26
30
|
!docs/connection_pooling.md
|
27
31
|
!docs/wiki/**/*.md
|
data/.rubocop.yml
CHANGED
data/CLAUDE.md
CHANGED
@@ -6,19 +6,38 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|
6
6
|
|
7
7
|
### Testing
|
8
8
|
|
9
|
-
Tryouts framework rules
|
10
|
-
1
|
11
|
-
2
|
12
|
-
3
|
13
|
-
4
|
14
|
-
5
|
15
|
-
6
|
16
|
-
7
|
17
|
-
|
18
|
-
|
19
|
-
- **
|
20
|
-
- **
|
21
|
-
|
9
|
+
**Tryouts framework rules:**
|
10
|
+
1. **Structure**: Each file has 3 sections - setup (optional), testcases, teardown (optional); each testcase has 3 required parts: description, code, expectations.
|
11
|
+
2. **Test cases**: Use `##` line prefix for test descriptions, Ruby code, then `#=>` expectations
|
12
|
+
3. **Variables**: Instance variables (`@var`) persist across sections; local variables do not.
|
13
|
+
4. **Expectations**: Multiple expectation types available (`#=>`, `#==>`, `#=:>`, `#=!>`, etc.); each testcase can have multiple expectations.
|
14
|
+
5. **Comments**: Use single `#` prefix, but DO NOT label file sections
|
15
|
+
6. **Philosophy**: Plain realistic code, avoid mocks/test DSL
|
16
|
+
7. **Result**: Last expression in each test case is the result
|
17
|
+
|
18
|
+
**Running tests:**
|
19
|
+
- **Basic**: `bundle exec try` (auto-discovers `*_try.rb` and `*.try.rb` files)
|
20
|
+
- **All options**: `bundle exec try --help` (complete CLI reference with agent-specific notes)
|
21
|
+
|
22
|
+
**Agent-optimized workflow:**
|
23
|
+
- **Default agent mode**: `bundle exec try --agent` (structured, token-efficient output for LLMs)
|
24
|
+
- **Focus modes**: `bundle exec try --agent --agent-focus summary` (options: `summary|first-failure|critical`)
|
25
|
+
- `summary`: Overview of test results only
|
26
|
+
- `first-failure`: Stop at first failure with details
|
27
|
+
- `critical`: Only show critical issues and summary
|
28
|
+
|
29
|
+
**Framework integration:**
|
30
|
+
- **RSpec**: `bundle exec try --rspec` (generates RSpec-compatible output)
|
31
|
+
- **Minitest**: `bundle exec try --minitest` (generates Minitest-compatible output)
|
32
|
+
|
33
|
+
**Debugging options:**
|
34
|
+
- **Stack traces**: `bundle exec try -s` (stack traces without debug logging)
|
35
|
+
- **Debug mode**: `bundle exec try -D` (additional logging including stack traces)
|
36
|
+
- **Verbose failures**: `bundle exec try -vf` (detailed failure output)
|
37
|
+
- **Fresh context**: `bundle exec try --fresh-context` (isolate test cases)
|
38
|
+
|
39
|
+
*Note: Use `--agent` mode for optimal token efficiency when analyzing test results programmatically.*
|
40
|
+
|
22
41
|
|
23
42
|
### Development Setup
|
24
43
|
- **Install dependencies**: `bundle install`
|
data/Gemfile
CHANGED
@@ -9,7 +9,7 @@ group :test do
|
|
9
9
|
gem 'tryouts', path: '../tryouts'
|
10
10
|
gem 'uri-valkey', path: '..//uri-valkey/gems', glob: 'uri-valkey.gemspec'
|
11
11
|
else
|
12
|
-
gem 'tryouts', '~> 3.
|
12
|
+
gem 'tryouts', '~> 3.5.1', require: false
|
13
13
|
end
|
14
14
|
gem 'concurrent-ruby', '~> 1.3.5', require: false
|
15
15
|
gem 'ruby-prof'
|
@@ -19,13 +19,13 @@ end
|
|
19
19
|
group :development, :test do
|
20
20
|
# byebug only works with MRI
|
21
21
|
gem 'byebug', '~> 11.0', require: false if RUBY_ENGINE == 'ruby'
|
22
|
+
gem 'irb', '~> 1.15.2', require: false
|
22
23
|
gem 'kramdown', require: false # Required for YARD markdown processing
|
23
24
|
gem 'pry-byebug', '~> 3.10.1', require: false if RUBY_ENGINE == 'ruby'
|
24
25
|
gem 'rubocop', require: false
|
25
26
|
gem 'rubocop-performance', require: false
|
26
27
|
gem 'rubocop-thread_safety', require: false
|
27
28
|
gem 'yard', '~> 0.9', require: false
|
28
|
-
gem 'irb', '~> 1.15.2', require: false
|
29
29
|
end
|
30
30
|
|
31
31
|
group :optional do
|
data/Gemfile.lock
CHANGED
@@ -113,7 +113,7 @@ GEM
|
|
113
113
|
ruby-progressbar (1.13.0)
|
114
114
|
stackprof (0.2.27)
|
115
115
|
stringio (3.1.7)
|
116
|
-
tryouts (3.
|
116
|
+
tryouts (3.5.1)
|
117
117
|
concurrent-ruby (~> 1.0)
|
118
118
|
irb
|
119
119
|
minitest (~> 5.0)
|
@@ -148,7 +148,7 @@ DEPENDENCIES
|
|
148
148
|
rubocop-thread_safety
|
149
149
|
ruby-prof
|
150
150
|
stackprof
|
151
|
-
tryouts (~> 3.
|
151
|
+
tryouts (~> 3.5.1)
|
152
152
|
yard (~> 0.9)
|
153
153
|
|
154
154
|
BUNDLED WITH
|
@@ -104,6 +104,42 @@ client.auth_token.expose { |token| make_api_call(token) }
|
|
104
104
|
client.auth_token.clear! # Explicit cleanup
|
105
105
|
```
|
106
106
|
|
107
|
+
#### Relationships
|
108
|
+
```ruby
|
109
|
+
class Customer < Familia::Horreum
|
110
|
+
feature :relationships
|
111
|
+
|
112
|
+
identifier_field :custid
|
113
|
+
field :custid, :name, :email
|
114
|
+
|
115
|
+
# Define relationship collections
|
116
|
+
tracked_in :active_users, type: :sorted_set
|
117
|
+
indexed_by :email_lookup, field: :email
|
118
|
+
set :domains
|
119
|
+
end
|
120
|
+
|
121
|
+
class Domain < Familia::Horreum
|
122
|
+
feature :relationships
|
123
|
+
|
124
|
+
identifier_field :domain_id
|
125
|
+
field :domain_id, :name
|
126
|
+
|
127
|
+
# Declare membership in customer collections
|
128
|
+
member_of Customer, :domains, type: :set
|
129
|
+
end
|
130
|
+
|
131
|
+
# Usage
|
132
|
+
customer = Customer.new(custid: "cust123", name: "Acme Corp")
|
133
|
+
domain = Domain.new(domain_id: "dom456", name: "acme.com")
|
134
|
+
|
135
|
+
# Establish bidirectional relationships
|
136
|
+
domain.add_to_customer_domains(customer.custid)
|
137
|
+
customer.domains.add(domain.identifier)
|
138
|
+
|
139
|
+
# Query relationships
|
140
|
+
domain.in_customer_domains?(customer.custid) # => true
|
141
|
+
```
|
142
|
+
|
107
143
|
#### Quantization
|
108
144
|
```ruby
|
109
145
|
class Metric < Familia::Horreum
|
@@ -487,10 +523,6 @@ end
|
|
487
523
|
class Model < Familia::Horreum
|
488
524
|
feature :expiration
|
489
525
|
|
490
|
-
def has_ttl_support?
|
491
|
-
self.class.features_enabled.include?(:expiration)
|
492
|
-
end
|
493
|
-
|
494
526
|
def available_features
|
495
527
|
self.class.features_enabled
|
496
528
|
end
|
@@ -498,7 +530,6 @@ end
|
|
498
530
|
|
499
531
|
model = Model.new
|
500
532
|
model.available_features # => [:expiration]
|
501
|
-
model.has_ttl_support? # => true
|
502
533
|
```
|
503
534
|
|
504
535
|
## Testing Features
|
data/docs/wiki/Home.md
CHANGED
@@ -10,15 +10,15 @@ Welcome to the comprehensive documentation for Familia v2.0. This wiki covers al
|
|
10
10
|
2. **[Transient Fields Guide](Transient-Fields-Guide.md)** - Non-persistent secure data handling with RedactedString
|
11
11
|
3. **[Security Model](Security-Model.md)** - Cryptographic design and Ruby memory limitations
|
12
12
|
|
13
|
-
### 🏗️ Architecture & System Design
|
13
|
+
### 🏗️ Architecture & System Design
|
14
14
|
|
15
15
|
4. **[Feature System Guide](Feature-System-Guide.md)** - Modular architecture with dependencies and conflict resolution _(new!)_
|
16
16
|
5. **[Connection Pooling Guide](Connection-Pooling-Guide.md)** - Provider pattern for efficient Redis/Valkey pooling _(new!)_
|
17
|
-
6. **[
|
17
|
+
6. **[Relationships Guide](Relationships-Guide.md)** - Object relationships and membership system _(new!)_
|
18
18
|
|
19
19
|
### 🛠️ Implementation & Usage
|
20
20
|
|
21
|
-
7. **[Implementation Guide](Implementation-Guide.md)** - Configuration, providers, and advanced usage
|
21
|
+
7. **[Implementation Guide](Implementation-Guide.md)** - Configuration, providers, and advanced usage
|
22
22
|
8. **[API Reference](API-Reference.md)** - Complete class and method documentation
|
23
23
|
|
24
24
|
### 🚀 Operations (As Needed)
|
@@ -52,7 +52,7 @@ class Customer < Familia::Horreum
|
|
52
52
|
feature :safe_dump # API-safe serialization
|
53
53
|
feature :expiration # TTL support
|
54
54
|
feature :encrypted_fields # Secure storage
|
55
|
-
|
55
|
+
|
56
56
|
field :name, :email
|
57
57
|
encrypted_field :api_key
|
58
58
|
default_expiration 24.hours
|
@@ -66,36 +66,46 @@ end
|
|
66
66
|
Familia.connection_provider = lambda do |uri|
|
67
67
|
parsed = URI.parse(uri)
|
68
68
|
pool_key = "#{parsed.host}:#{parsed.port}/#{parsed.db || 0}"
|
69
|
-
|
69
|
+
|
70
70
|
@pools[pool_key] ||= ConnectionPool.new(size: 10) do
|
71
71
|
Redis.new(host: parsed.host, port: parsed.port, db: parsed.db || 0)
|
72
72
|
end
|
73
|
-
|
73
|
+
|
74
74
|
@pools[pool_key].with { |conn| conn }
|
75
75
|
end
|
76
76
|
```
|
77
77
|
|
78
|
-
### Object Relationships
|
78
|
+
### Object Relationships
|
79
79
|
```ruby
|
80
80
|
class Customer < Familia::Horreum
|
81
|
-
feature :
|
82
|
-
|
83
|
-
field :name, :email
|
81
|
+
feature :relationships
|
82
|
+
identifier_field :custid
|
83
|
+
field :custid, :name, :email
|
84
|
+
|
85
|
+
# Customer collections
|
86
|
+
set :domains
|
84
87
|
end
|
85
88
|
|
86
|
-
class Domain < Familia::Horreum
|
87
|
-
feature :
|
88
|
-
|
89
|
-
field :name, :dns_zone
|
89
|
+
class Domain < Familia::Horreum
|
90
|
+
feature :relationships
|
91
|
+
identifier_field :domain_id
|
92
|
+
field :domain_id, :name, :dns_zone
|
93
|
+
|
94
|
+
# Declare membership in customer collections
|
95
|
+
member_of Customer, :domains, type: :set
|
90
96
|
end
|
91
97
|
|
92
|
-
# Create objects
|
93
|
-
customer = Customer.new(name: "Acme Corp")
|
94
|
-
domain = Domain.new(name: "acme.com")
|
98
|
+
# Create objects and establish relationships
|
99
|
+
customer = Customer.new(custid: "cust123", name: "Acme Corp")
|
100
|
+
domain = Domain.new(domain_id: "dom456", name: "acme.com")
|
101
|
+
|
102
|
+
# Establish bidirectional relationship
|
103
|
+
domain.add_to_customer_domains(customer.custid)
|
104
|
+
customer.domains.add(domain.identifier)
|
95
105
|
|
96
|
-
#
|
97
|
-
|
98
|
-
|
106
|
+
# Query relationships
|
107
|
+
domain.in_customer_domains?(customer.custid) # => true
|
108
|
+
customer.domains.member?(domain.identifier) # => true
|
99
109
|
```
|
100
110
|
|
101
111
|
|