static_association 0.1.0 → 0.2.0
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 +5 -5
- data/.github/workflows/lint.yml +15 -0
- data/.github/workflows/test.yml +24 -0
- data/.gitignore +1 -0
- data/.standard.yml +3 -0
- data/Appraisals +6 -20
- data/CHANGELOG.md +19 -0
- data/CODEOWNERS +16 -0
- data/Gemfile +1 -1
- data/README.md +22 -6
- data/Rakefile +4 -3
- data/gemfiles/{4.0.gemfile → 7.1.gemfile} +2 -2
- data/gemfiles/{4.1.gemfile → 7.2.gemfile} +2 -2
- data/gemfiles/{4.2.gemfile → 8.0.gemfile} +2 -2
- data/lib/static_association/version.rb +1 -1
- data/lib/static_association.rb +62 -20
- data/spec/spec_helper.rb +3 -5
- data/spec/static_association_spec.rb +364 -87
- data/static_association.gemspec +15 -19
- metadata +21 -54
- data/.travis.yml +0 -17
- data/gemfiles/3.0.gemfile +0 -8
- data/gemfiles/3.1.gemfile +0 -8
- data/gemfiles/3.2.gemfile +0 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 86ead7939a0117761cc96d89b8bead9dd9d6158e6266a8d068c9033b55fa727a
|
4
|
+
data.tar.gz: b8e8fed19948fa1c38dd7b975c083af75c1e232f89010d197ca661bb48c6a422
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a524a9345a9b43d5f6c9651e2be95cdc56095fca59a5cf7e97b032e424d122fe1769d365caed9c993ef01b49f6fa77cdcab23b1e841a731392ffe0ebd7707883
|
7
|
+
data.tar.gz: 5a54cf10f28c4582cd9aa9d353defc0c8fe853014be4c883563f7f7b6bbad0d856a0f5d889ec5ca8f16c04d1f1ebf56fc71506dfdd7d776e6c754bf8c74a5e02
|
@@ -0,0 +1,24 @@
|
|
1
|
+
name: Test
|
2
|
+
|
3
|
+
on: push
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
test:
|
7
|
+
strategy:
|
8
|
+
fail-fast: false
|
9
|
+
matrix:
|
10
|
+
ruby: ['3.2', '3.3', '3.4']
|
11
|
+
gemfile: ['7.1', '7.2', '8.0']
|
12
|
+
|
13
|
+
runs-on: ubuntu-latest
|
14
|
+
|
15
|
+
env:
|
16
|
+
BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile
|
17
|
+
|
18
|
+
steps:
|
19
|
+
- uses: actions/checkout@v3
|
20
|
+
- uses: ruby/setup-ruby@v1
|
21
|
+
with:
|
22
|
+
ruby-version: ${{ matrix.ruby }}
|
23
|
+
bundler-cache: true
|
24
|
+
- run: bundle exec rspec
|
data/.gitignore
CHANGED
data/.standard.yml
ADDED
data/Appraisals
CHANGED
@@ -1,25 +1,11 @@
|
|
1
|
-
appraise "
|
2
|
-
gem "
|
3
|
-
gem "activesupport", "3.0"
|
1
|
+
appraise "7.1" do
|
2
|
+
gem "activesupport", "7.1"
|
4
3
|
end
|
5
4
|
|
6
|
-
appraise "
|
7
|
-
gem "
|
8
|
-
gem "activesupport", "3.1"
|
5
|
+
appraise "7.2" do
|
6
|
+
gem "activesupport", "7.2"
|
9
7
|
end
|
10
8
|
|
11
|
-
appraise "
|
12
|
-
gem "activesupport", "
|
13
|
-
end
|
14
|
-
|
15
|
-
appraise "4.0" do
|
16
|
-
gem "activesupport", "4.0"
|
17
|
-
end
|
18
|
-
|
19
|
-
appraise "4.1" do
|
20
|
-
gem "activesupport", "4.1"
|
21
|
-
end
|
22
|
-
|
23
|
-
appraise "4.2" do
|
24
|
-
gem "activesupport", "4.2"
|
9
|
+
appraise "8.0" do
|
10
|
+
gem "activesupport", "8.0"
|
25
11
|
end
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
## v0.2.0 (20 June 2025)
|
4
|
+
|
5
|
+
### Added
|
6
|
+
|
7
|
+
- `standardrb` gem for linting (#11)
|
8
|
+
- `.where` method for finding multiple records with an array of IDs (#16)
|
9
|
+
- `.ids` method which returns an array of all record IDs (#25)
|
10
|
+
- `.find_by` method to return the first method matching a specific condition (#26)
|
11
|
+
- `.find_by!` method which behaves like `find_by` but raises when a matching
|
12
|
+
record is not found (#28)
|
13
|
+
|
14
|
+
### Changed
|
15
|
+
- Use GitHub Actions for CI (#10)
|
16
|
+
- Refactor specs to be more inline with thoughtbot style (#15)
|
17
|
+
- Require `activesupport` v7.1.0 or higher (#22)
|
18
|
+
- Coerce `.find_by_id` argument to integer (#23)
|
19
|
+
- More descriptive exception messages (#24)
|
data/CODEOWNERS
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# Lines starting with '#' are comments.
|
2
|
+
# Each line is a file pattern followed by one or more owners.
|
3
|
+
|
4
|
+
# More details are here: https://help.github.com/articles/about-codeowners/
|
5
|
+
|
6
|
+
# The '*' pattern is global owners.
|
7
|
+
|
8
|
+
# Order is important. The last matching pattern has the most precedence.
|
9
|
+
# The folders are ordered as follows:
|
10
|
+
|
11
|
+
# In each subsection folders are ordered first by depth, then alphabetically.
|
12
|
+
# This should make it easy to add new rules without breaking existing ones.
|
13
|
+
|
14
|
+
# Global rule:
|
15
|
+
|
16
|
+
* @sidane
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# StaticAssociation
|
2
2
|
|
3
|
-
|
3
|
+

|
4
|
+

|
4
5
|
|
5
6
|
Adds basic ActiveRecord-like associations to static data.
|
6
7
|
|
@@ -9,7 +10,7 @@ Adds basic ActiveRecord-like associations to static data.
|
|
9
10
|
Add this line to your application's Gemfile:
|
10
11
|
|
11
12
|
```ruby
|
12
|
-
gem
|
13
|
+
gem "static_association"
|
13
14
|
```
|
14
15
|
|
15
16
|
And then execute:
|
@@ -38,13 +39,28 @@ class Day
|
|
38
39
|
end
|
39
40
|
```
|
40
41
|
|
41
|
-
Calling `record` will allow you to create an instance of this static model,
|
42
|
+
Calling `record` will allow you to create an instance of this static model,
|
43
|
+
a unique id is mandatory. The newly created object is yielded to the passed
|
44
|
+
block.
|
42
45
|
|
43
|
-
The `Day` class will gain
|
46
|
+
The `Day` class will gain the following methods:
|
47
|
+
|
48
|
+
- `.all`: returns all the static records defined in the class.
|
49
|
+
- `.ids`: returns an array of all the ids of the static records.
|
50
|
+
- `.find`: accepts a single id and returns the matching record. If the record
|
51
|
+
does not exist, a `RecordNotFound` error is raised.
|
52
|
+
- `.find_by_id`: behaves similarly to the `.find` method, except it returns
|
53
|
+
`nil` when a record does not exist.
|
54
|
+
- `.find_by`: finds the first record matching the specified conditions. If no
|
55
|
+
record is found, returns `nil`.
|
56
|
+
- `find_by!` behaves like `find_by` but raises a
|
57
|
+
`StaticAssociation::RecordNotFound` error if no record is found.
|
58
|
+
- `.where`: accepts an array of ids and returns all records with matching ids.
|
44
59
|
|
45
60
|
### Associations
|
46
61
|
|
47
|
-
Currently just a
|
62
|
+
Currently just a `belongs_to` association can be created. This behaviour can be
|
63
|
+
mixed into an `ActiveRecord` model:
|
48
64
|
|
49
65
|
```ruby
|
50
66
|
class Event < ActiveRecord::Base
|
@@ -61,6 +77,6 @@ This assumes your model has a field `day_id`.
|
|
61
77
|
1. Fork it
|
62
78
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
63
79
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
64
|
-
4. Run
|
80
|
+
4. Run lint checks and tests (`bundle exec rake`)
|
65
81
|
5. Push to the branch (`git push origin my-new-feature`)
|
66
82
|
6. Create new Pull Request
|
data/Rakefile
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
|
-
require
|
3
|
-
require
|
2
|
+
require "rspec/core/rake_task"
|
3
|
+
require "appraisal"
|
4
|
+
require "standard/rake"
|
4
5
|
|
5
6
|
RSpec::Core::RakeTask.new(:spec)
|
6
7
|
|
7
|
-
task :
|
8
|
+
task default: [:standard, :spec]
|
data/lib/static_association.rb
CHANGED
@@ -1,15 +1,20 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
1
|
+
require "static_association/version"
|
2
|
+
require "active_support/concern"
|
3
|
+
require "active_support/core_ext/module/delegation"
|
4
|
+
require "active_support/core_ext/hash/keys"
|
5
|
+
require "active_support/core_ext/string/inflections"
|
6
6
|
|
7
7
|
module StaticAssociation
|
8
8
|
extend ActiveSupport::Concern
|
9
9
|
|
10
|
+
class ArgumentError < StandardError; end
|
11
|
+
|
10
12
|
class DuplicateID < StandardError; end
|
13
|
+
|
11
14
|
class RecordNotFound < StandardError; end
|
12
15
|
|
16
|
+
class UndefinedAttribute < StandardError; end
|
17
|
+
|
13
18
|
attr_reader :id
|
14
19
|
|
15
20
|
private
|
@@ -24,46 +29,83 @@ module StaticAssociation
|
|
24
29
|
delegate :each, to: :all
|
25
30
|
|
26
31
|
def index
|
27
|
-
@index ||= {}
|
32
|
+
@index ||= {}
|
28
33
|
end
|
29
34
|
|
30
35
|
def all
|
31
36
|
index.values
|
32
37
|
end
|
33
38
|
|
39
|
+
def ids
|
40
|
+
index.keys
|
41
|
+
end
|
42
|
+
|
34
43
|
def find(id)
|
35
|
-
find_by_id(id) or raise RecordNotFound
|
44
|
+
find_by_id(id) or raise RecordNotFound.new(
|
45
|
+
"Couldn't find DummyClass with 'id'=#{id}"
|
46
|
+
)
|
36
47
|
end
|
37
48
|
|
38
49
|
def find_by_id(id)
|
39
|
-
index[
|
50
|
+
index[
|
51
|
+
Integer(id, exception: false) || id
|
52
|
+
]
|
53
|
+
end
|
54
|
+
|
55
|
+
def where(id: [])
|
56
|
+
all.select { |record| id.include?(record.id) }
|
57
|
+
end
|
58
|
+
|
59
|
+
def find_by(**args)
|
60
|
+
args.any? or raise ArgumentError
|
61
|
+
|
62
|
+
all.find { |record| matches_attributes?(record: record, attributes: args) }
|
63
|
+
end
|
64
|
+
|
65
|
+
def find_by!(**args)
|
66
|
+
find_by(**args) or raise RecordNotFound.new(
|
67
|
+
"Couldn't find #{name} with " +
|
68
|
+
args.map { |k, v| "#{k}=#{v}" }.join(", ")
|
69
|
+
)
|
40
70
|
end
|
41
71
|
|
42
72
|
def record(settings, &block)
|
43
73
|
settings.assert_valid_keys(:id)
|
44
74
|
id = settings.fetch(:id)
|
45
|
-
|
46
|
-
|
47
|
-
|
75
|
+
|
76
|
+
if index.has_key?(id)
|
77
|
+
raise DuplicateID.new("Duplicate record with 'id'=#{id} found")
|
78
|
+
end
|
79
|
+
|
80
|
+
record = new(id)
|
81
|
+
record.instance_exec(record, &block) if block
|
48
82
|
index[id] = record
|
49
83
|
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def matches_attributes?(record:, attributes:)
|
88
|
+
attributes.all? do |attribute, value|
|
89
|
+
record.respond_to?(attribute) or raise UndefinedAttribute.new(
|
90
|
+
"Undefined attribute '#{attribute}'"
|
91
|
+
)
|
92
|
+
|
93
|
+
record.public_send(attribute) == value
|
94
|
+
end
|
95
|
+
end
|
50
96
|
end
|
51
97
|
|
52
98
|
module AssociationHelpers
|
53
99
|
def belongs_to_static(name, opts = {})
|
54
100
|
class_name = opts.fetch(:class_name, name.to_s.camelize)
|
55
101
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
class_name.constantize.find(foreign_key) if foreign_key
|
60
|
-
rescue RecordNotFound
|
61
|
-
nil
|
62
|
-
end
|
102
|
+
send(:define_method, name) do
|
103
|
+
foreign_key = send(:"#{name}_id")
|
104
|
+
class_name.constantize.find_by_id(foreign_key)
|
63
105
|
end
|
64
106
|
|
65
|
-
|
66
|
-
|
107
|
+
send(:define_method, "#{name}=") do |assoc|
|
108
|
+
send(:"#{name}_id=", assoc.id)
|
67
109
|
end
|
68
110
|
end
|
69
111
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -4,12 +4,10 @@
|
|
4
4
|
# loaded once.
|
5
5
|
#
|
6
6
|
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
|
-
require
|
7
|
+
require "bundler/setup"
|
8
8
|
|
9
|
-
require
|
10
|
-
|
11
|
-
require 'static_association'
|
9
|
+
require "static_association"
|
12
10
|
|
13
11
|
RSpec.configure do |config|
|
14
|
-
config.order =
|
12
|
+
config.order = "random"
|
15
13
|
end
|
@@ -1,142 +1,419 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "spec_helper"
|
2
|
+
require "static_association"
|
3
3
|
|
4
|
-
|
4
|
+
class DummyClass
|
5
|
+
include StaticAssociation
|
6
|
+
attr_accessor :name
|
7
|
+
end
|
5
8
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
end
|
9
|
+
class AssociationClass
|
10
|
+
attr_accessor :dummy_class_id
|
11
|
+
attr_accessor :dodo_class_id
|
10
12
|
|
13
|
+
extend StaticAssociation::AssociationHelpers
|
14
|
+
belongs_to_static :dummy_class
|
15
|
+
belongs_to_static :dodo_class, class_name: "DummyClass"
|
16
|
+
end
|
17
|
+
|
18
|
+
RSpec.describe StaticAssociation do
|
11
19
|
after do
|
12
|
-
DummyClass.instance_variable_set(
|
20
|
+
DummyClass.instance_variable_set(:@index, {})
|
13
21
|
end
|
14
22
|
|
15
23
|
describe ".record" do
|
16
|
-
it "
|
17
|
-
expect {
|
18
|
-
|
19
|
-
self.name = 'asdf'
|
20
|
-
end
|
21
|
-
}.to change(DummyClass, :count).by(1)
|
24
|
+
it "adds a record" do
|
25
|
+
expect { DummyClass.record(id: 1) { self.name = "test" } }
|
26
|
+
.to change(DummyClass, :count).by(1)
|
22
27
|
end
|
23
28
|
|
24
|
-
context "
|
25
|
-
it "
|
26
|
-
|
27
|
-
DummyClass.record id: 1 do
|
28
|
-
self.name = 'asdf'
|
29
|
-
end
|
29
|
+
context "when using `self`" do
|
30
|
+
it "assigns attributes" do
|
31
|
+
record = DummyClass.record(id: 1) { self.name = "test" }
|
30
32
|
|
31
|
-
|
32
|
-
|
33
|
-
end
|
34
|
-
}.to raise_error(StaticAssociation::DuplicateID)
|
33
|
+
expect(record.id).to eq(1)
|
34
|
+
expect(record.name).to eq("test")
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
|
-
context "
|
39
|
-
|
40
|
-
DummyClass.record
|
41
|
-
self.name = 'asdf'
|
42
|
-
end
|
43
|
-
}
|
38
|
+
context "when using the object" do
|
39
|
+
it "assigns attributes" do
|
40
|
+
record = DummyClass.record(id: 1) { |i| i.name = "test" }
|
44
41
|
|
45
|
-
|
46
|
-
|
42
|
+
expect(record.id).to eq(1)
|
43
|
+
expect(record.name).to eq("test")
|
44
|
+
end
|
47
45
|
end
|
48
46
|
|
49
|
-
context "
|
50
|
-
|
51
|
-
DummyClass.record
|
52
|
-
c.name = 'asdf'
|
53
|
-
end
|
54
|
-
}
|
47
|
+
context "when the id is a duplicate" do
|
48
|
+
it "raises an error" do
|
49
|
+
DummyClass.record(id: 1) { self.name = "test0" }
|
55
50
|
|
56
|
-
|
57
|
-
|
51
|
+
expect { DummyClass.record(id: 1) { self.name = "test1" } }
|
52
|
+
.to raise_error(
|
53
|
+
StaticAssociation::DuplicateID,
|
54
|
+
"Duplicate record with 'id'=1 found"
|
55
|
+
)
|
56
|
+
end
|
58
57
|
end
|
59
58
|
|
59
|
+
context "when an attribute is not defined" do
|
60
|
+
it "raises an error" do
|
61
|
+
expect { DummyClass.record(id: 1) { self.foo = "bar" } }
|
62
|
+
.to raise_error(NoMethodError)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context "when a key is invalid" do
|
67
|
+
it "raises an error" do
|
68
|
+
expect { DummyClass.record(id: 1, foo: "bar") }
|
69
|
+
.to raise_error(ArgumentError)
|
70
|
+
end
|
71
|
+
end
|
60
72
|
|
61
73
|
context "without a block" do
|
62
|
-
|
74
|
+
it "adds a record" do
|
75
|
+
expect { DummyClass.record(id: 1) }.to change(DummyClass, :count).by(1)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe ".all" do
|
81
|
+
it "returns all records" do
|
82
|
+
record1 = DummyClass.record(id: 1)
|
83
|
+
record2 = DummyClass.record(id: 2)
|
84
|
+
|
85
|
+
records = DummyClass.all
|
63
86
|
|
64
|
-
|
65
|
-
its(:name) { should be_nil }
|
87
|
+
expect(records).to contain_exactly(record1, record2)
|
66
88
|
end
|
89
|
+
end
|
67
90
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
91
|
+
describe ".ids" do
|
92
|
+
it "returns array of ids for all records" do
|
93
|
+
_record1 = DummyClass.record(id: 1)
|
94
|
+
_record2 = DummyClass.record(id: 2)
|
95
|
+
|
96
|
+
ids = DummyClass.ids
|
97
|
+
|
98
|
+
expect(ids).to contain_exactly(1, 2)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
describe ".find" do
|
103
|
+
context "when the record exists" do
|
104
|
+
it "returns the record" do
|
105
|
+
record = DummyClass.record(id: 1)
|
106
|
+
|
107
|
+
found_record = DummyClass.find(1)
|
108
|
+
|
109
|
+
expect(found_record).to eq(record)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
context "when the record does not exist" do
|
114
|
+
it "raises an error" do
|
115
|
+
expect { DummyClass.find(1) }
|
116
|
+
.to raise_error(
|
117
|
+
StaticAssociation::RecordNotFound,
|
118
|
+
"Couldn't find DummyClass with 'id'=1"
|
119
|
+
)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
context "when argument is numeric string" do
|
124
|
+
it "returns the record" do
|
125
|
+
record = DummyClass.record(id: 1)
|
126
|
+
|
127
|
+
found_record = DummyClass.find("1")
|
128
|
+
|
129
|
+
expect(found_record).to eq(record)
|
73
130
|
end
|
74
131
|
end
|
75
132
|
end
|
76
133
|
|
77
|
-
describe "
|
78
|
-
|
79
|
-
|
80
|
-
|
134
|
+
describe ".find_by_id" do
|
135
|
+
context "when the record exists" do
|
136
|
+
it "returns the record" do
|
137
|
+
record = DummyClass.record(id: 1)
|
138
|
+
|
139
|
+
found_record = DummyClass.find_by_id(1)
|
140
|
+
|
141
|
+
expect(found_record).to eq(record)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
context "when the record does not exist" do
|
146
|
+
it "returns nil" do
|
147
|
+
found_record = DummyClass.find_by_id(1)
|
148
|
+
|
149
|
+
expect(found_record).to be_nil
|
81
150
|
end
|
82
151
|
end
|
83
152
|
|
84
|
-
|
85
|
-
|
86
|
-
|
153
|
+
context "when id argument is a numeric string" do
|
154
|
+
it "returns the record" do
|
155
|
+
record = DummyClass.record(id: 1)
|
156
|
+
|
157
|
+
found_record = DummyClass.find_by_id("1")
|
158
|
+
|
159
|
+
expect(found_record).to eq(record)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
context "when id argument is a non-numeric string" do
|
164
|
+
it "returns the record" do
|
165
|
+
DummyClass.record(id: 1)
|
166
|
+
|
167
|
+
found_record = DummyClass.find_by_id("foo")
|
168
|
+
|
169
|
+
expect(found_record).to be_nil
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
context "when record ids are strings and id argument matches a record" do
|
174
|
+
it "returns the record" do
|
175
|
+
record = DummyClass.record(id: "foo")
|
176
|
+
|
177
|
+
found_record = DummyClass.find_by_id("foo")
|
87
178
|
|
88
|
-
|
89
|
-
|
179
|
+
expect(found_record).to eq(record)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
context "when record ids are strings and id argument doesn't match a record" do
|
184
|
+
it "returns nil" do
|
185
|
+
DummyClass.record(id: "foo")
|
186
|
+
|
187
|
+
found_record = DummyClass.find_by_id("bar")
|
188
|
+
|
189
|
+
expect(found_record).to be_nil
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
describe ".where" do
|
195
|
+
it "returns all records with the given ids" do
|
196
|
+
record1 = DummyClass.record(id: 1)
|
197
|
+
_record2 = DummyClass.record(id: 2)
|
198
|
+
record3 = DummyClass.record(id: 3)
|
199
|
+
|
200
|
+
results = DummyClass.where(id: [1, 3, 4])
|
201
|
+
|
202
|
+
expect(results).to contain_exactly(record1, record3)
|
203
|
+
end
|
204
|
+
|
205
|
+
describe ".find_by" do
|
206
|
+
context "when record exists with the specified attribute value" do
|
207
|
+
it "returns the record" do
|
208
|
+
record1 = DummyClass.record(id: 1) do |r|
|
209
|
+
r.name = "foo"
|
210
|
+
end
|
211
|
+
_record2 = DummyClass.record(id: 2) do |r|
|
212
|
+
r.name = "bar"
|
213
|
+
end
|
214
|
+
|
215
|
+
found_record = DummyClass.find_by(name: "foo")
|
216
|
+
|
217
|
+
expect(found_record).to eq(record1)
|
218
|
+
end
|
90
219
|
end
|
91
220
|
|
92
|
-
context "record
|
93
|
-
it "
|
221
|
+
context "when no record exists that matches the specified attribute value" do
|
222
|
+
it "returns nil" do
|
223
|
+
DummyClass.record(id: 1) do |r|
|
224
|
+
r.name = "foo"
|
225
|
+
end
|
226
|
+
|
227
|
+
found_record = DummyClass.find_by(name: "bar")
|
228
|
+
|
229
|
+
expect(found_record).to be_nil
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
context "when multiple records match the specified attribute value" do
|
234
|
+
it "returns the first matching record" do
|
235
|
+
record1 = DummyClass.record(id: 1) do |r|
|
236
|
+
r.name = "foo"
|
237
|
+
end
|
238
|
+
_record2 = DummyClass.record(id: 2) do |r|
|
239
|
+
r.name = "foo"
|
240
|
+
end
|
241
|
+
|
242
|
+
found_record = DummyClass.find_by(name: "foo")
|
243
|
+
|
244
|
+
expect(found_record).to eq(record1)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
context "when specifying multiple attribute values" do
|
249
|
+
it "returns the record matching all attributes" do
|
250
|
+
_record1 = DummyClass.record(id: 1) do |r|
|
251
|
+
r.name = "foo"
|
252
|
+
end
|
253
|
+
record2 = DummyClass.record(id: 2) do |r|
|
254
|
+
r.name = "foo"
|
255
|
+
end
|
256
|
+
|
257
|
+
found_record = DummyClass.find_by(id: 2, name: "foo")
|
258
|
+
|
259
|
+
expect(found_record).to eq(record2)
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
context "when specifying multiple attribute values but no record " \
|
264
|
+
"matches all attributes" do
|
265
|
+
it "returns nil" do
|
266
|
+
_record1 = DummyClass.record(id: 1) do |r|
|
267
|
+
r.name = "foo"
|
268
|
+
end
|
269
|
+
|
270
|
+
found_record = DummyClass.find_by(id: 1, name: "bar")
|
271
|
+
|
272
|
+
expect(found_record).to be_nil
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
context "with undefined attributes" do
|
277
|
+
it "raises an error" do
|
278
|
+
DummyClass.record(id: 1)
|
279
|
+
|
280
|
+
expect {
|
281
|
+
DummyClass.find_by(undefined_attribute: 1)
|
282
|
+
}.to raise_error(
|
283
|
+
StaticAssociation::UndefinedAttribute,
|
284
|
+
"Undefined attribute 'undefined_attribute'"
|
285
|
+
)
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
context "with no attributes" do
|
290
|
+
it "raises a StaticAssociation::ArgumentError" do
|
94
291
|
expect {
|
95
|
-
DummyClass.
|
96
|
-
}.to raise_error(StaticAssociation::
|
292
|
+
DummyClass.find_by
|
293
|
+
}.to raise_error(StaticAssociation::ArgumentError)
|
97
294
|
end
|
98
295
|
end
|
99
296
|
end
|
297
|
+
end
|
100
298
|
|
101
|
-
|
102
|
-
|
103
|
-
|
299
|
+
describe ".find_by!" do
|
300
|
+
context "when record exists with the specified attribute value" do
|
301
|
+
it "returns the record" do
|
302
|
+
record1 = DummyClass.record(id: 1) do |r|
|
303
|
+
r.name = "foo"
|
304
|
+
end
|
305
|
+
_record2 = DummyClass.record(id: 2) do |r|
|
306
|
+
r.name = "bar"
|
307
|
+
end
|
308
|
+
|
309
|
+
found_record = DummyClass.find_by!(name: "foo")
|
104
310
|
|
105
|
-
|
106
|
-
its(:id) { should == 1 }
|
311
|
+
expect(found_record).to eq(record1)
|
107
312
|
end
|
313
|
+
end
|
108
314
|
|
109
|
-
|
110
|
-
|
111
|
-
|
315
|
+
context "when no record exists that matches the specified attribute value" do
|
316
|
+
it "raises an error" do
|
317
|
+
DummyClass.record(id: 1) do |r|
|
318
|
+
r.name = "foo"
|
319
|
+
end
|
320
|
+
|
321
|
+
expect {
|
322
|
+
DummyClass.find_by!(name: "bar")
|
323
|
+
}.to raise_error(
|
324
|
+
StaticAssociation::RecordNotFound,
|
325
|
+
"Couldn't find DummyClass with name=bar"
|
326
|
+
)
|
112
327
|
end
|
113
328
|
end
|
114
|
-
end
|
115
329
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
330
|
+
context "when multiple records match the specified attribute value" do
|
331
|
+
it "returns the first matching record" do
|
332
|
+
record1 = DummyClass.record(id: 1) do |r|
|
333
|
+
r.name = "foo"
|
334
|
+
end
|
335
|
+
_record2 = DummyClass.record(id: 2) do |r|
|
336
|
+
r.name = "foo"
|
337
|
+
end
|
338
|
+
|
339
|
+
found_record = DummyClass.find_by!(name: "foo")
|
340
|
+
|
341
|
+
expect(found_record).to eq(record1)
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
context "when specifying multiple attribute values" do
|
346
|
+
it "returns the record matching all attributes" do
|
347
|
+
_record1 = DummyClass.record(id: 1) do |r|
|
348
|
+
r.name = "foo"
|
349
|
+
end
|
350
|
+
record2 = DummyClass.record(id: 2) do |r|
|
351
|
+
r.name = "foo"
|
352
|
+
end
|
353
|
+
|
354
|
+
found_record = DummyClass.find_by!(id: 2, name: "foo")
|
355
|
+
|
356
|
+
expect(found_record).to eq(record2)
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
context "when specifying multiple attribute values but no record " \
|
361
|
+
"matches all attributes" do
|
362
|
+
it "raises an error" do
|
363
|
+
_record1 = DummyClass.record(id: 1) do |r|
|
364
|
+
r.name = "foo"
|
365
|
+
end
|
366
|
+
|
367
|
+
expect {
|
368
|
+
DummyClass.find_by!(id: 1, name: "bar")
|
369
|
+
}.to raise_error(
|
370
|
+
StaticAssociation::RecordNotFound,
|
371
|
+
"Couldn't find DummyClass with id=1, name=bar"
|
372
|
+
)
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
context "with undefined attributes" do
|
377
|
+
it "raises a StaticAssociation::UndefinedAttribute" do
|
378
|
+
DummyClass.record(id: 1)
|
379
|
+
|
380
|
+
expect {
|
381
|
+
DummyClass.find_by!(undefined_attribute: 1)
|
382
|
+
}.to raise_error(
|
383
|
+
StaticAssociation::UndefinedAttribute,
|
384
|
+
"Undefined attribute 'undefined_attribute'"
|
385
|
+
)
|
386
|
+
end
|
387
|
+
end
|
120
388
|
|
121
|
-
|
122
|
-
|
123
|
-
|
389
|
+
context "with no attributes" do
|
390
|
+
it "raises a StaticAssociation::ArgumentError" do
|
391
|
+
expect {
|
392
|
+
DummyClass.find_by!
|
393
|
+
}.to raise_error(StaticAssociation::ArgumentError)
|
394
|
+
end
|
124
395
|
end
|
396
|
+
end
|
125
397
|
|
126
|
-
|
398
|
+
describe ".belongs_to_static" do
|
399
|
+
it "defines a reader method for the association" do
|
400
|
+
associated_class = AssociationClass.new
|
401
|
+
allow(DummyClass).to receive(:find_by_id)
|
127
402
|
|
128
|
-
it "creates reader method that uses the correct singularized class when finding static association" do
|
129
|
-
expect {
|
130
|
-
DummyClass.should_receive(:find)
|
131
|
-
}
|
132
403
|
associated_class.dummy_class
|
404
|
+
|
405
|
+
expect(DummyClass).to have_received(:find_by_id)
|
133
406
|
end
|
134
407
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
408
|
+
context "when `class_name` is specified" do
|
409
|
+
it "defines a reader method for the association" do
|
410
|
+
associated_class = AssociationClass.new
|
411
|
+
allow(DummyClass).to receive(:find_by_id)
|
412
|
+
|
413
|
+
associated_class.dodo_class
|
414
|
+
|
415
|
+
expect(DummyClass).to have_received(:find_by_id)
|
416
|
+
end
|
140
417
|
end
|
141
418
|
end
|
142
419
|
end
|
data/static_association.gemspec
CHANGED
@@ -1,28 +1,24 @@
|
|
1
|
-
|
2
|
-
lib = File.expand_path('../lib', __FILE__)
|
1
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require
|
3
|
+
require "static_association/version"
|
5
4
|
|
6
5
|
Gem::Specification.new do |spec|
|
7
|
-
spec.name
|
8
|
-
spec.version
|
9
|
-
spec.authors
|
10
|
-
spec.email
|
11
|
-
spec.description
|
12
|
-
spec.summary
|
13
|
-
spec.license
|
14
|
-
spec.homepage
|
6
|
+
spec.name = "static_association"
|
7
|
+
spec.version = StaticAssociation::VERSION
|
8
|
+
spec.authors = ["Oliver Nightingale"]
|
9
|
+
spec.email = ["oliver.nightingale1@gmail.com"]
|
10
|
+
spec.description = "StaticAssociation adds a simple enum type that can act like an ActiveRecord association for static data."
|
11
|
+
spec.summary = "ActiveRecord like associations for static data"
|
12
|
+
spec.license = "MIT"
|
13
|
+
spec.homepage = "https://github.com/thoughtbot/static_association"
|
15
14
|
|
16
|
-
spec.files
|
17
|
-
spec.executables
|
18
|
-
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
15
|
+
spec.files = `git ls-files`.split($/)
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
19
17
|
spec.require_paths = ["lib"]
|
20
18
|
|
21
|
-
spec.add_dependency "activesupport", ">=
|
19
|
+
spec.add_dependency "activesupport", ">= 7.1.0"
|
22
20
|
|
23
|
-
spec.add_development_dependency "
|
24
|
-
spec.add_development_dependency "rspec", "~> 3.4"
|
25
|
-
spec.add_development_dependency "rspec-its", "~> 1.2"
|
26
|
-
spec.add_development_dependency "rake"
|
21
|
+
spec.add_development_dependency "rspec"
|
27
22
|
spec.add_development_dependency "appraisal"
|
23
|
+
spec.add_development_dependency "standard"
|
28
24
|
end
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: static_association
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Oliver Nightingale
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: activesupport
|
@@ -16,58 +15,30 @@ dependencies:
|
|
16
15
|
requirements:
|
17
16
|
- - ">="
|
18
17
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
18
|
+
version: 7.1.0
|
20
19
|
type: :runtime
|
21
20
|
prerelease: false
|
22
21
|
version_requirements: !ruby/object:Gem::Requirement
|
23
22
|
requirements:
|
24
23
|
- - ">="
|
25
24
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: bundler
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - "~>"
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '1.3'
|
34
|
-
type: :development
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - "~>"
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '1.3'
|
25
|
+
version: 7.1.0
|
41
26
|
- !ruby/object:Gem::Dependency
|
42
27
|
name: rspec
|
43
28
|
requirement: !ruby/object:Gem::Requirement
|
44
29
|
requirements:
|
45
|
-
- - "
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '3.4'
|
48
|
-
type: :development
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - "~>"
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: '3.4'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: rspec-its
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - "~>"
|
30
|
+
- - ">="
|
60
31
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
32
|
+
version: '0'
|
62
33
|
type: :development
|
63
34
|
prerelease: false
|
64
35
|
version_requirements: !ruby/object:Gem::Requirement
|
65
36
|
requirements:
|
66
|
-
- - "
|
37
|
+
- - ">="
|
67
38
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
39
|
+
version: '0'
|
69
40
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
41
|
+
name: appraisal
|
71
42
|
requirement: !ruby/object:Gem::Requirement
|
72
43
|
requirements:
|
73
44
|
- - ">="
|
@@ -81,7 +52,7 @@ dependencies:
|
|
81
52
|
- !ruby/object:Gem::Version
|
82
53
|
version: '0'
|
83
54
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
55
|
+
name: standard
|
85
56
|
requirement: !ruby/object:Gem::Requirement
|
86
57
|
requirements:
|
87
58
|
- - ">="
|
@@ -102,29 +73,29 @@ executables: []
|
|
102
73
|
extensions: []
|
103
74
|
extra_rdoc_files: []
|
104
75
|
files:
|
76
|
+
- ".github/workflows/lint.yml"
|
77
|
+
- ".github/workflows/test.yml"
|
105
78
|
- ".gitignore"
|
106
|
-
- ".
|
79
|
+
- ".standard.yml"
|
107
80
|
- Appraisals
|
81
|
+
- CHANGELOG.md
|
82
|
+
- CODEOWNERS
|
108
83
|
- Gemfile
|
109
84
|
- LICENSE.txt
|
110
85
|
- README.md
|
111
86
|
- Rakefile
|
112
|
-
- gemfiles/
|
113
|
-
- gemfiles/
|
114
|
-
- gemfiles/
|
115
|
-
- gemfiles/4.0.gemfile
|
116
|
-
- gemfiles/4.1.gemfile
|
117
|
-
- gemfiles/4.2.gemfile
|
87
|
+
- gemfiles/7.1.gemfile
|
88
|
+
- gemfiles/7.2.gemfile
|
89
|
+
- gemfiles/8.0.gemfile
|
118
90
|
- lib/static_association.rb
|
119
91
|
- lib/static_association/version.rb
|
120
92
|
- spec/spec_helper.rb
|
121
93
|
- spec/static_association_spec.rb
|
122
94
|
- static_association.gemspec
|
123
|
-
homepage: https://github.com/
|
95
|
+
homepage: https://github.com/thoughtbot/static_association
|
124
96
|
licenses:
|
125
97
|
- MIT
|
126
98
|
metadata: {}
|
127
|
-
post_install_message:
|
128
99
|
rdoc_options: []
|
129
100
|
require_paths:
|
130
101
|
- lib
|
@@ -139,11 +110,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
139
110
|
- !ruby/object:Gem::Version
|
140
111
|
version: '0'
|
141
112
|
requirements: []
|
142
|
-
|
143
|
-
rubygems_version: 2.4.5
|
144
|
-
signing_key:
|
113
|
+
rubygems_version: 3.6.9
|
145
114
|
specification_version: 4
|
146
115
|
summary: ActiveRecord like associations for static data
|
147
|
-
test_files:
|
148
|
-
- spec/spec_helper.rb
|
149
|
-
- spec/static_association_spec.rb
|
116
|
+
test_files: []
|
data/.travis.yml
DELETED
@@ -1,17 +0,0 @@
|
|
1
|
-
language: ruby
|
2
|
-
install: bundle install
|
3
|
-
sudo: false
|
4
|
-
script:
|
5
|
-
- bundle exec rake
|
6
|
-
rvm:
|
7
|
-
- 2.2
|
8
|
-
- 2.1
|
9
|
-
- 2.0
|
10
|
-
- 1.9.3
|
11
|
-
gemfile:
|
12
|
-
- gemfiles/3.0.gemfile
|
13
|
-
- gemfiles/3.1.gemfile
|
14
|
-
- gemfiles/3.2.gemfile
|
15
|
-
- gemfiles/4.0.gemfile
|
16
|
-
- gemfiles/4.1.gemfile
|
17
|
-
- gemfiles/4.2.gemfile
|
data/gemfiles/3.0.gemfile
DELETED
data/gemfiles/3.1.gemfile
DELETED