static_association 0.1.0 → 0.3.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: 5666e88a0802eea2fe4650de42230959d14a792dd161c50ee81c1abeb681df12
4
+ data.tar.gz: fd5d870372e99f9ba199eef79c0550b673b707d051831ed35197489de6c5e017
5
5
  SHA512:
6
- metadata.gz: 2430eb60d4dfee691c90cceb8607ed4021ab21fbee8918a13d53ef2acb1ae4e628957717ddb005df1676037de734968d3a97b0e2fd59fccb8e4369057a89e075
7
- data.tar.gz: 2a5e771bb651c00f24068ce82e5b10307b2a7c4d7ce6729f50e329424adfc2772aa61c99a2c4fd5094575b7aec91fe97b13032485cbed1d12daf3ba3d4becfe5
6
+ metadata.gz: 8b6462940e27e5abccfe7938e63efceed99418e8dc3b808ab42926525e914835d9a42b25e49942039c5108ca90b63fbb2adaab23bb4ef9a0c6c13069bbd00949
7
+ data.tar.gz: 4b594996748a63859e1326ab389b23dd17d8ba67e2c3b12b3ed63d4d13b1539be2851c85abd2e7b712e7a5c15bcd9f0753d33871caf79aa53e7bc38c4b6e181e
@@ -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,24 @@
1
+ # Changelog
2
+
3
+ ## v0.3.0 (24 October 2025)
4
+
5
+ - Add `attr_evaluated` to define attributes that are evaluated/computed at runtime (#32)
6
+ - Use including class name for error messages (#31)
7
+
8
+ ## v0.2.0 (20 June 2025)
9
+
10
+ ### Added
11
+
12
+ - `standardrb` gem for linting (#11)
13
+ - `.where` method for finding multiple records with an array of IDs (#16)
14
+ - `.ids` method which returns an array of all record IDs (#25)
15
+ - `.find_by` method to return the first method matching a specific condition (#26)
16
+ - `.find_by!` method which behaves like `find_by` but raises when a matching
17
+ record is not found (#28)
18
+
19
+ ### Changed
20
+ - Use GitHub Actions for CI (#10)
21
+ - Refactor specs to be more inline with thoughtbot style (#15)
22
+ - Require `activesupport` v7.1.0 or higher (#22)
23
+ - Coerce `.find_by_id` argument to integer (#23)
24
+ - 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.3.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,99 @@ 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 #{name} 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
+ def attr_evaluated(*names)
86
+ attr_writer(*names)
87
+
88
+ names.each do |name|
89
+ define_method(name) do
90
+ attr_value = instance_variable_get("@#{name}")
91
+
92
+ if attr_value.respond_to?(:call)
93
+ instance_exec(&attr_value)
94
+ else
95
+ attr_value
96
+ end
97
+ end
98
+ end
99
+ end
100
+
101
+ private
102
+
103
+ def matches_attributes?(record:, attributes:)
104
+ attributes.all? do |attribute, value|
105
+ record.respond_to?(attribute) or raise UndefinedAttribute.new(
106
+ "Undefined attribute '#{attribute}'"
107
+ )
108
+
109
+ record.public_send(attribute) == value
110
+ end
111
+ end
50
112
  end
51
113
 
52
114
  module AssociationHelpers
53
115
  def belongs_to_static(name, opts = {})
54
116
  class_name = opts.fetch(:class_name, name.to_s.camelize)
55
117
 
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
118
+ send(:define_method, name) do
119
+ foreign_key = send(:"#{name}_id")
120
+ class_name.constantize.find_by_id(foreign_key)
63
121
  end
64
122
 
65
- self.send(:define_method, "#{name}=") do |assoc|
66
- self.send("#{name}_id=", assoc.id)
123
+ send(:define_method, "#{name}=") do |assoc|
124
+ send(:"#{name}_id=", assoc.id)
67
125
  end
68
126
  end
69
127
  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,472 @@
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
5
6
 
6
- class DummyClass
7
- include StaticAssociation
8
- attr_accessor :name
9
- end
7
+ attr_accessor :name
8
+ attr_evaluated :gender
9
+ end
10
+
11
+ class AssociationClass
12
+ attr_accessor :dummy_class_id
13
+ attr_accessor :dodo_class_id
14
+
15
+ extend StaticAssociation::AssociationHelpers
16
+
17
+ belongs_to_static :dummy_class
18
+ belongs_to_static :dodo_class, class_name: "DummyClass"
19
+ end
10
20
 
21
+ RSpec.describe StaticAssociation do
11
22
  after do
12
- DummyClass.instance_variable_set("@index", {})
23
+ DummyClass.instance_variable_set(:@index, {})
13
24
  end
14
25
 
15
26
  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)
27
+ it "adds a record" do
28
+ expect { DummyClass.record(id: 1) { self.name = "test" } }
29
+ .to change(DummyClass, :count).by(1)
22
30
  end
23
31
 
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
32
+ context "when using `self`" do
33
+ it "assigns attributes" do
34
+ record = DummyClass.record(id: 1) { self.name = "test" }
30
35
 
31
- DummyClass.record id: 1 do
32
- self.name = 'asdf'
36
+ expect(record.id).to eq(1)
37
+ expect(record.name).to eq("test")
38
+ end
39
+ end
40
+
41
+ context "when using the object" do
42
+ it "assigns attributes" do
43
+ record = DummyClass.record(id: 1) { |i| i.name = "test" }
44
+
45
+ expect(record.id).to eq(1)
46
+ expect(record.name).to eq("test")
47
+ end
48
+ end
49
+
50
+ context "when the id is a duplicate" do
51
+ it "raises an error" do
52
+ DummyClass.record(id: 1) { self.name = "test0" }
53
+
54
+ expect { DummyClass.record(id: 1) { self.name = "test1" } }
55
+ .to raise_error(
56
+ StaticAssociation::DuplicateID,
57
+ "Duplicate record with 'id'=1 found"
58
+ )
59
+ end
60
+ end
61
+
62
+ context "when an attribute is not defined" do
63
+ it "raises an error" do
64
+ expect { DummyClass.record(id: 1) { self.foo = "bar" } }
65
+ .to raise_error(NoMethodError)
66
+ end
67
+ end
68
+
69
+ context "when a key is invalid" do
70
+ it "raises an error" do
71
+ expect { DummyClass.record(id: 1, foo: "bar") }
72
+ .to raise_error(ArgumentError)
73
+ end
74
+ end
75
+
76
+ context "without a block" do
77
+ it "adds a record" do
78
+ expect { DummyClass.record(id: 1) }.to change(DummyClass, :count).by(1)
79
+ end
80
+ end
81
+
82
+ context "when attr_evaluated defined attribute is assigned a lambda" do
83
+ it "evaluates the lambda when the attribute is read" do
84
+ record = DummyClass.record(id: 1) do
85
+ self.gender = -> do
86
+ if name == "Jane"
87
+ :female
88
+ else
89
+ :male
90
+ end
33
91
  end
34
- }.to raise_error(StaticAssociation::DuplicateID)
92
+ end
93
+
94
+ expect(record.gender).to eq(:male)
35
95
  end
36
96
  end
37
97
 
38
- context "sets up the instance using self" do
39
- subject {
40
- DummyClass.record id: 1 do
41
- self.name = 'asdf'
98
+ context "when attr_evaluated defined attribute is assigned a proc" do
99
+ it "evaluates the proc when the attribute is read" do
100
+ record = DummyClass.record(id: 1) do
101
+ self.name = "Jane"
102
+ self.gender = proc do
103
+ if @name == "Jane"
104
+ :female
105
+ else
106
+ :male
107
+ end
108
+ end
42
109
  end
43
- }
44
110
 
45
- its(:id) { should == 1 }
46
- its(:name) { should == 'asdf' }
111
+ expect(record.gender).to eq(:female)
112
+ end
47
113
  end
48
114
 
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'
115
+ context "when attr_evaluated defined attribute is assigned a static value" do
116
+ it "returns the assigned value when the attribute is read" do
117
+ record = DummyClass.record(id: 1) do
118
+ self.gender = "Not disclosed"
53
119
  end
54
- }
55
120
 
56
- its(:id) { should == 1 }
57
- its(:name) { should == 'asdf' }
121
+ expect(record.gender).to eq("Not disclosed")
122
+ end
58
123
  end
124
+ end
59
125
 
126
+ describe ".all" do
127
+ it "returns all records" do
128
+ record1 = DummyClass.record(id: 1)
129
+ record2 = DummyClass.record(id: 2)
60
130
 
61
- context "without a block" do
62
- subject { DummyClass.record id: 1 }
131
+ records = DummyClass.all
63
132
 
64
- its(:id) { should == 1 }
65
- its(:name) { should be_nil }
133
+ expect(records).to contain_exactly(record1, record2)
66
134
  end
135
+ end
67
136
 
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)
137
+ describe ".ids" do
138
+ it "returns array of ids for all records" do
139
+ _record1 = DummyClass.record(id: 1)
140
+ _record2 = DummyClass.record(id: 2)
141
+
142
+ ids = DummyClass.ids
143
+
144
+ expect(ids).to contain_exactly(1, 2)
145
+ end
146
+ end
147
+
148
+ describe ".find" do
149
+ context "when the record exists" do
150
+ it "returns the record" do
151
+ record = DummyClass.record(id: 1)
152
+
153
+ found_record = DummyClass.find(1)
154
+
155
+ expect(found_record).to eq(record)
156
+ end
157
+ end
158
+
159
+ context "when the record does not exist" do
160
+ it "raises an error" do
161
+ expect { DummyClass.find(1) }
162
+ .to raise_error(
163
+ StaticAssociation::RecordNotFound,
164
+ "Couldn't find DummyClass with 'id'=1"
165
+ )
166
+
167
+ stub_const("NewDummyClass", Class.new(DummyClass))
168
+ expect { NewDummyClass.find(1) }
169
+ .to raise_error(
170
+ StaticAssociation::RecordNotFound,
171
+ "Couldn't find NewDummyClass with 'id'=1"
172
+ )
173
+ end
174
+ end
175
+
176
+ context "when argument is numeric string" do
177
+ it "returns the record" do
178
+ record = DummyClass.record(id: 1)
179
+
180
+ found_record = DummyClass.find("1")
181
+
182
+ expect(found_record).to eq(record)
73
183
  end
74
184
  end
75
185
  end
76
186
 
77
- describe "finders" do
78
- before do
79
- DummyClass.record id: 1 do
80
- self.name = 'asdf'
187
+ describe ".find_by_id" do
188
+ context "when the record exists" do
189
+ it "returns the record" do
190
+ record = DummyClass.record(id: 1)
191
+
192
+ found_record = DummyClass.find_by_id(1)
193
+
194
+ expect(found_record).to eq(record)
81
195
  end
82
196
  end
83
197
 
84
- describe ".find" do
85
- context "record exists" do
86
- subject { DummyClass.find(1) }
198
+ context "when the record does not exist" do
199
+ it "returns nil" do
200
+ found_record = DummyClass.find_by_id(1)
87
201
 
88
- it { should be_kind_of(DummyClass) }
89
- its(:id) { should == 1 }
202
+ expect(found_record).to be_nil
90
203
  end
204
+ end
205
+
206
+ context "when id argument is a numeric string" do
207
+ it "returns the record" do
208
+ record = DummyClass.record(id: 1)
209
+
210
+ found_record = DummyClass.find_by_id("1")
91
211
 
92
- context "record does not exist" do
93
- it "should raise a StaticAssociation::RecordNotFoundError" do
212
+ expect(found_record).to eq(record)
213
+ end
214
+ end
215
+
216
+ context "when id argument is a non-numeric string" do
217
+ it "returns the record" do
218
+ DummyClass.record(id: 1)
219
+
220
+ found_record = DummyClass.find_by_id("foo")
221
+
222
+ expect(found_record).to be_nil
223
+ end
224
+ end
225
+
226
+ context "when record ids are strings and id argument matches a record" do
227
+ it "returns the record" do
228
+ record = DummyClass.record(id: "foo")
229
+
230
+ found_record = DummyClass.find_by_id("foo")
231
+
232
+ expect(found_record).to eq(record)
233
+ end
234
+ end
235
+
236
+ context "when record ids are strings and id argument doesn't match a record" do
237
+ it "returns nil" do
238
+ DummyClass.record(id: "foo")
239
+
240
+ found_record = DummyClass.find_by_id("bar")
241
+
242
+ expect(found_record).to be_nil
243
+ end
244
+ end
245
+ end
246
+
247
+ describe ".where" do
248
+ it "returns all records with the given ids" do
249
+ record1 = DummyClass.record(id: 1)
250
+ _record2 = DummyClass.record(id: 2)
251
+ record3 = DummyClass.record(id: 3)
252
+
253
+ results = DummyClass.where(id: [1, 3, 4])
254
+
255
+ expect(results).to contain_exactly(record1, record3)
256
+ end
257
+
258
+ describe ".find_by" do
259
+ context "when record exists with the specified attribute value" do
260
+ it "returns the record" do
261
+ record1 = DummyClass.record(id: 1) do |r|
262
+ r.name = "foo"
263
+ end
264
+ _record2 = DummyClass.record(id: 2) do |r|
265
+ r.name = "bar"
266
+ end
267
+
268
+ found_record = DummyClass.find_by(name: "foo")
269
+
270
+ expect(found_record).to eq(record1)
271
+ end
272
+ end
273
+
274
+ context "when no record exists that matches the specified attribute value" do
275
+ it "returns nil" do
276
+ DummyClass.record(id: 1) do |r|
277
+ r.name = "foo"
278
+ end
279
+
280
+ found_record = DummyClass.find_by(name: "bar")
281
+
282
+ expect(found_record).to be_nil
283
+ end
284
+ end
285
+
286
+ context "when multiple records match the specified attribute value" do
287
+ it "returns the first matching record" do
288
+ record1 = DummyClass.record(id: 1) do |r|
289
+ r.name = "foo"
290
+ end
291
+ _record2 = DummyClass.record(id: 2) do |r|
292
+ r.name = "foo"
293
+ end
294
+
295
+ found_record = DummyClass.find_by(name: "foo")
296
+
297
+ expect(found_record).to eq(record1)
298
+ end
299
+ end
300
+
301
+ context "when specifying multiple attribute values" do
302
+ it "returns the record matching all attributes" do
303
+ _record1 = DummyClass.record(id: 1) do |r|
304
+ r.name = "foo"
305
+ end
306
+ record2 = DummyClass.record(id: 2) do |r|
307
+ r.name = "foo"
308
+ end
309
+
310
+ found_record = DummyClass.find_by(id: 2, name: "foo")
311
+
312
+ expect(found_record).to eq(record2)
313
+ end
314
+ end
315
+
316
+ context "when specifying multiple attribute values but no record " \
317
+ "matches all attributes" do
318
+ it "returns nil" do
319
+ _record1 = DummyClass.record(id: 1) do |r|
320
+ r.name = "foo"
321
+ end
322
+
323
+ found_record = DummyClass.find_by(id: 1, name: "bar")
324
+
325
+ expect(found_record).to be_nil
326
+ end
327
+ end
328
+
329
+ context "with undefined attributes" do
330
+ it "raises an error" do
331
+ DummyClass.record(id: 1)
332
+
333
+ expect {
334
+ DummyClass.find_by(undefined_attribute: 1)
335
+ }.to raise_error(
336
+ StaticAssociation::UndefinedAttribute,
337
+ "Undefined attribute 'undefined_attribute'"
338
+ )
339
+ end
340
+ end
341
+
342
+ context "with no attributes" do
343
+ it "raises a StaticAssociation::ArgumentError" do
94
344
  expect {
95
- DummyClass.find(:not_in_the_index)
96
- }.to raise_error(StaticAssociation::RecordNotFound)
345
+ DummyClass.find_by
346
+ }.to raise_error(StaticAssociation::ArgumentError)
97
347
  end
98
348
  end
99
349
  end
350
+ end
100
351
 
101
- describe ".find_by_id" do
102
- context "record exists" do
103
- subject { DummyClass.find_by_id(1) }
352
+ describe ".find_by!" do
353
+ context "when record exists with the specified attribute value" do
354
+ it "returns the record" do
355
+ record1 = DummyClass.record(id: 1) do |r|
356
+ r.name = "foo"
357
+ end
358
+ _record2 = DummyClass.record(id: 2) do |r|
359
+ r.name = "bar"
360
+ end
361
+
362
+ found_record = DummyClass.find_by!(name: "foo")
104
363
 
105
- it { should be_kind_of(DummyClass) }
106
- its(:id) { should == 1 }
364
+ expect(found_record).to eq(record1)
107
365
  end
366
+ end
367
+
368
+ context "when no record exists that matches the specified attribute value" do
369
+ it "raises an error" do
370
+ DummyClass.record(id: 1) do |r|
371
+ r.name = "foo"
372
+ end
108
373
 
109
- context "record does not exist" do
110
- subject { DummyClass.find_by_id(:not_in_the_index) }
111
- it { should be_nil }
374
+ expect {
375
+ DummyClass.find_by!(name: "bar")
376
+ }.to raise_error(
377
+ StaticAssociation::RecordNotFound,
378
+ "Couldn't find DummyClass with name=bar"
379
+ )
112
380
  end
113
381
  end
114
- end
115
382
 
116
- describe ".belongs_to_static" do
117
- class AssociationClass
118
- attr_accessor :dummy_class_id
119
- attr_accessor :dodo_class_id
383
+ context "when multiple records match the specified attribute value" do
384
+ it "returns the first matching record" do
385
+ record1 = DummyClass.record(id: 1) do |r|
386
+ r.name = "foo"
387
+ end
388
+ _record2 = DummyClass.record(id: 2) do |r|
389
+ r.name = "foo"
390
+ end
391
+
392
+ found_record = DummyClass.find_by!(name: "foo")
393
+
394
+ expect(found_record).to eq(record1)
395
+ end
396
+ end
397
+
398
+ context "when specifying multiple attribute values" do
399
+ it "returns the record matching all attributes" do
400
+ _record1 = DummyClass.record(id: 1) do |r|
401
+ r.name = "foo"
402
+ end
403
+ record2 = DummyClass.record(id: 2) do |r|
404
+ r.name = "foo"
405
+ end
406
+
407
+ found_record = DummyClass.find_by!(id: 2, name: "foo")
408
+
409
+ expect(found_record).to eq(record2)
410
+ end
411
+ end
412
+
413
+ context "when specifying multiple attribute values but no record " \
414
+ "matches all attributes" do
415
+ it "raises an error" do
416
+ _record1 = DummyClass.record(id: 1) do |r|
417
+ r.name = "foo"
418
+ end
419
+
420
+ expect {
421
+ DummyClass.find_by!(id: 1, name: "bar")
422
+ }.to raise_error(
423
+ StaticAssociation::RecordNotFound,
424
+ "Couldn't find DummyClass with id=1, name=bar"
425
+ )
426
+ end
427
+ end
428
+
429
+ context "with undefined attributes" do
430
+ it "raises a StaticAssociation::UndefinedAttribute" do
431
+ DummyClass.record(id: 1)
432
+
433
+ expect {
434
+ DummyClass.find_by!(undefined_attribute: 1)
435
+ }.to raise_error(
436
+ StaticAssociation::UndefinedAttribute,
437
+ "Undefined attribute 'undefined_attribute'"
438
+ )
439
+ end
440
+ end
120
441
 
121
- extend StaticAssociation::AssociationHelpers
122
- belongs_to_static :dummy_class
123
- belongs_to_static :dodo_class, class_name: 'DummyClass'
442
+ context "with no attributes" do
443
+ it "raises a StaticAssociation::ArgumentError" do
444
+ expect {
445
+ DummyClass.find_by!
446
+ }.to raise_error(StaticAssociation::ArgumentError)
447
+ end
124
448
  end
449
+ end
125
450
 
126
- let(:associated_class) { AssociationClass.new }
451
+ describe ".belongs_to_static" do
452
+ it "defines a reader method for the association" do
453
+ associated_class = AssociationClass.new
454
+ allow(DummyClass).to receive(:find_by_id)
127
455
 
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
456
  associated_class.dummy_class
457
+
458
+ expect(DummyClass).to have_received(:find_by_id)
133
459
  end
134
460
 
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
461
+ context "when `class_name` is specified" do
462
+ it "defines a reader method for the association" do
463
+ associated_class = AssociationClass.new
464
+ allow(DummyClass).to receive(:find_by_id)
465
+
466
+ associated_class.dodo_class
467
+
468
+ expect(DummyClass).to have_received(:find_by_id)
469
+ end
140
470
  end
141
471
  end
142
472
  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.3.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 => "../"