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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: dd88c2ff7fc56c68c7afb52e5ecc34d05d00ff06
4
- data.tar.gz: 247e7c6ec09c4aeabc7405caa0af4895ea2527c2
2
+ SHA256:
3
+ metadata.gz: 86ead7939a0117761cc96d89b8bead9dd9d6158e6266a8d068c9033b55fa727a
4
+ data.tar.gz: b8e8fed19948fa1c38dd7b975c083af75c1e232f89010d197ca661bb48c6a422
5
5
  SHA512:
6
- metadata.gz: 2430eb60d4dfee691c90cceb8607ed4021ab21fbee8918a13d53ef2acb1ae4e628957717ddb005df1676037de734968d3a97b0e2fd59fccb8e4369057a89e075
7
- data.tar.gz: 2a5e771bb651c00f24068ce82e5b10307b2a7c4d7ce6729f50e329424adfc2772aa61c99a2c4fd5094575b7aec91fe97b13032485cbed1d12daf3ba3d4becfe5
6
+ metadata.gz: a524a9345a9b43d5f6c9651e2be95cdc56095fca59a5cf7e97b032e424d122fe1769d365caed9c993ef01b49f6fa77cdcab23b1e841a731392ffe0ebd7707883
7
+ data.tar.gz: 5a54cf10f28c4582cd9aa9d353defc0c8fe853014be4c883563f7f7b6bbad0d856a0f5d889ec5ca8f16c04d1f1ebf56fc71506dfdd7d776e6c754bf8c74a5e02
@@ -0,0 +1,15 @@
1
+ name: Lint
2
+
3
+ on: push
4
+
5
+ jobs:
6
+ lint:
7
+ runs-on: ubuntu-latest
8
+
9
+ steps:
10
+ - uses: actions/checkout@v3
11
+ - uses: ruby/setup-ruby@v1
12
+ with:
13
+ ruby-version: '3.1'
14
+ bundler-cache: true
15
+ - run: bundle exec standardrb
@@ -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
@@ -4,6 +4,7 @@
4
4
  .bundle
5
5
  .config
6
6
  .yardoc
7
+ .tool-versions
7
8
  Gemfile.lock
8
9
  InstalledFiles
9
10
  _yardoc
data/.standard.yml ADDED
@@ -0,0 +1,3 @@
1
+ fix: false
2
+ parallel: false
3
+ format: progress
data/Appraisals CHANGED
@@ -1,25 +1,11 @@
1
- appraise "3.0" do
2
- gem "i18n"
3
- gem "activesupport", "3.0"
1
+ appraise "7.1" do
2
+ gem "activesupport", "7.1"
4
3
  end
5
4
 
6
- appraise "3.1" do
7
- gem "i18n"
8
- gem "activesupport", "3.1"
5
+ appraise "7.2" do
6
+ gem "activesupport", "7.2"
9
7
  end
10
8
 
11
- appraise "3.2" do
12
- gem "activesupport", "3.2"
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
@@ -1,4 +1,4 @@
1
- source 'https://rubygems.org'
1
+ source "https://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in static_association.gemspec
4
4
  gemspec
data/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # StaticAssociation
2
2
 
3
- [![Build Status](https://travis-ci.org/New-Bamboo/static_association.png?branch=master)](https://travis-ci.org/New-Bamboo/static_association)
3
+ ![test](https://github.com/thoughtbot/static_association/actions/workflows/test.yml/badge.svg)
4
+ ![lint](https://github.com/thoughtbot/static_association/actions/workflows/lint.yml/badge.svg)
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 'static_association'
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, a unique id is mandatory. The newly created object is yielded to the passed block.
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 an `all` and `find` method.
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 'belongs to' association can be created. This behaviour can be mixed into an `ActiveRecord` model:
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 the tests (`rake`)
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 'rspec/core/rake_task'
3
- require 'appraisal'
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 :default => :spec
8
+ task default: [:standard, :spec]
@@ -2,6 +2,6 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "activesupport", "4.0"
5
+ gem "activesupport", "7.1"
6
6
 
7
- gemspec :path => "../"
7
+ gemspec path: "../"
@@ -2,6 +2,6 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "activesupport", "4.1"
5
+ gem "activesupport", "7.2"
6
6
 
7
- gemspec :path => "../"
7
+ gemspec path: "../"
@@ -2,6 +2,6 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "activesupport", "4.2"
5
+ gem "activesupport", "8.0"
6
6
 
7
- gemspec :path => "../"
7
+ gemspec path: "../"
@@ -1,3 +1,3 @@
1
1
  module StaticAssociation
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -1,15 +1,20 @@
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'
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[id]
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
- raise DuplicateID if index.has_key?(id)
46
- record = self.new(id)
47
- record.instance_exec(record, &block) if block_given?
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
- self.send(:define_method, name) do
57
- begin
58
- foreign_key = self.send("#{name}_id")
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
- self.send(:define_method, "#{name}=") do |assoc|
66
- self.send("#{name}_id=", assoc.id)
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 'bundler/setup'
7
+ require "bundler/setup"
8
8
 
9
- require 'rspec/its'
10
-
11
- require 'static_association'
9
+ require "static_association"
12
10
 
13
11
  RSpec.configure do |config|
14
- config.order = 'random'
12
+ config.order = "random"
15
13
  end
@@ -1,142 +1,419 @@
1
- require 'spec_helper'
2
- require 'static_association'
1
+ require "spec_helper"
2
+ require "static_association"
3
3
 
4
- describe StaticAssociation do
4
+ class DummyClass
5
+ include StaticAssociation
6
+ attr_accessor :name
7
+ end
5
8
 
6
- class DummyClass
7
- include StaticAssociation
8
- attr_accessor :name
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("@index", {})
20
+ DummyClass.instance_variable_set(:@index, {})
13
21
  end
14
22
 
15
23
  describe ".record" do
16
- it "should add a record" do
17
- expect {
18
- DummyClass.record id: 1 do
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 "id uniqueness" do
25
- it "should raise an error with a duplicate id" do
26
- expect {
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
- DummyClass.record id: 1 do
32
- self.name = 'asdf'
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 "sets up the instance using self" do
39
- subject {
40
- DummyClass.record id: 1 do
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
- its(:id) { should == 1 }
46
- its(:name) { should == 'asdf' }
42
+ expect(record.id).to eq(1)
43
+ expect(record.name).to eq("test")
44
+ end
47
45
  end
48
46
 
49
- context "sets up the instance using the object passed in" do
50
- subject {
51
- DummyClass.record id: 1 do |c|
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
- its(:id) { should == 1 }
57
- its(:name) { should == 'asdf' }
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
- subject { DummyClass.record id: 1 }
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
- its(:id) { should == 1 }
65
- its(:name) { should be_nil }
87
+ expect(records).to contain_exactly(record1, record2)
66
88
  end
89
+ end
67
90
 
68
- context "asserting valid keys" do
69
- it "should raise an error" do
70
- expect {
71
- DummyClass.record id: 1, foo: :bar
72
- }.to raise_error(ArgumentError)
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 "finders" do
78
- before do
79
- DummyClass.record id: 1 do
80
- self.name = 'asdf'
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
- describe ".find" do
85
- context "record exists" do
86
- subject { DummyClass.find(1) }
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
- it { should be_kind_of(DummyClass) }
89
- its(:id) { should == 1 }
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 does not exist" do
93
- it "should raise a StaticAssociation::RecordNotFoundError" do
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.find(:not_in_the_index)
96
- }.to raise_error(StaticAssociation::RecordNotFound)
292
+ DummyClass.find_by
293
+ }.to raise_error(StaticAssociation::ArgumentError)
97
294
  end
98
295
  end
99
296
  end
297
+ end
100
298
 
101
- describe ".find_by_id" do
102
- context "record exists" do
103
- subject { DummyClass.find_by_id(1) }
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
- it { should be_kind_of(DummyClass) }
106
- its(:id) { should == 1 }
311
+ expect(found_record).to eq(record1)
107
312
  end
313
+ end
108
314
 
109
- context "record does not exist" do
110
- subject { DummyClass.find_by_id(:not_in_the_index) }
111
- it { should be_nil }
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
- describe ".belongs_to_static" do
117
- class AssociationClass
118
- attr_accessor :dummy_class_id
119
- attr_accessor :dodo_class_id
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
- extend StaticAssociation::AssociationHelpers
122
- belongs_to_static :dummy_class
123
- belongs_to_static :dodo_class, class_name: 'DummyClass'
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
- let(:associated_class) { AssociationClass.new }
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
- it "creates a different reader method that uses the specified class when finding static asssociation" do
136
- expect {
137
- DummyClass.should_receive(:find)
138
- }
139
- associated_class.dodo_class
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
@@ -1,28 +1,24 @@
1
- # coding: utf-8
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 'static_association/version'
3
+ require "static_association/version"
5
4
 
6
5
  Gem::Specification.new do |spec|
7
- spec.name = "static_association"
8
- spec.version = StaticAssociation::VERSION
9
- spec.authors = ["Oliver Nightingale"]
10
- spec.email = ["oliver.nightingale1@gmail.com"]
11
- spec.description = %q{StaticAssociation adds a simple enum type that can act like an ActiveRecord association for static data.}
12
- spec.summary = %q{ActiveRecord like associations for static data}
13
- spec.license = "MIT"
14
- spec.homepage = "https://github.com/New-Bamboo/static_association"
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 = `git ls-files`.split($/)
17
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
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", ">= 3.0.0"
19
+ spec.add_dependency "activesupport", ">= 7.1.0"
22
20
 
23
- spec.add_development_dependency "bundler", "~> 1.3"
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.1.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: 2016-01-04 00:00:00.000000000 Z
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: 3.0.0
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: 3.0.0
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: '1.2'
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: '1.2'
39
+ version: '0'
69
40
  - !ruby/object:Gem::Dependency
70
- name: rake
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: appraisal
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
- - ".travis.yml"
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/3.0.gemfile
113
- - gemfiles/3.1.gemfile
114
- - gemfiles/3.2.gemfile
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/New-Bamboo/static_association
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
- rubyforge_project:
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
@@ -1,8 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "i18n"
6
- gem "activesupport", "3.0"
7
-
8
- gemspec :path => "../"
data/gemfiles/3.1.gemfile DELETED
@@ -1,8 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "i18n"
6
- gem "activesupport", "3.1"
7
-
8
- gemspec :path => "../"
data/gemfiles/3.2.gemfile DELETED
@@ -1,7 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "activesupport", "3.2"
6
-
7
- gemspec :path => "../"