static_association 0.0.2 → 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: e9070d8b187c9b04c979f3f77244f5f0aff54bc9
4
- data.tar.gz: 3a95140244d4d5413d29b86b517f847177a03182
2
+ SHA256:
3
+ metadata.gz: 86ead7939a0117761cc96d89b8bead9dd9d6158e6266a8d068c9033b55fa727a
4
+ data.tar.gz: b8e8fed19948fa1c38dd7b975c083af75c1e232f89010d197ca661bb48c6a422
5
5
  SHA512:
6
- metadata.gz: 11a3e13c6693ec96763180f4f1e7feff0d41bb6b8fd0ea8bac6f662c21cc23df81b1d2aa16e3107f887d44df54da0ab5be89e66d3e7b44b3f5dda73c94373d4b
7
- data.tar.gz: cc67b525ced9dce9f6b56ec235b955c7a140cf98639738451f1d4ec95285db2e5e42e65e880bd3b1f717a6c9fbb8ffd1d4a00f5d90c96435c7d069ac79125af1
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
@@ -1,8 +1,10 @@
1
1
  *.gem
2
+ *.gemfile.lock
2
3
  *.rbc
3
4
  .bundle
4
5
  .config
5
6
  .yardoc
7
+ .tool-versions
6
8
  Gemfile.lock
7
9
  InstalledFiles
8
10
  _yardoc
@@ -15,3 +17,4 @@ spec/reports
15
17
  test/tmp
16
18
  test/version_tmp
17
19
  tmp
20
+ vendor/bundle
data/.standard.yml ADDED
@@ -0,0 +1,3 @@
1
+ fix: false
2
+ parallel: false
3
+ format: progress
data/Appraisals CHANGED
@@ -1,17 +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"
9
+ appraise "8.0" do
10
+ gem "activesupport", "8.0"
17
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,14 +1,17 @@
1
1
  # StaticAssociation
2
2
 
3
- Adds basic ActiveRecord like associations to static data.
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
- This has been extracted from ProjectsDB and Hotleads, see the `BudgetCategory`, `Project` and `ArchiveReason`, `Lead` classes respectively for examples.
6
+ Adds basic ActiveRecord-like associations to static data.
6
7
 
7
8
  ## Installation
8
9
 
9
10
  Add this line to your application's Gemfile:
10
11
 
11
- gem 'static_association'
12
+ ```ruby
13
+ gem "static_association"
14
+ ```
12
15
 
13
16
  And then execute:
14
17
 
@@ -24,25 +27,48 @@ Or install it yourself as:
24
27
 
25
28
  Create your static association class:
26
29
 
27
- class Days
28
- include StaticAssociation
30
+ ```ruby
31
+ class Day
32
+ include StaticAssociation
29
33
 
30
- attr_accessor :name
34
+ attr_accessor :name
31
35
 
32
- record id: 0 do |day|
33
- day.name = :monday
34
- end
35
- end
36
+ record id: 0 do |day|
37
+ day.name = :monday
38
+ end
39
+ end
40
+ ```
36
41
 
37
- 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.
38
45
 
39
- The `Days` 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.
40
59
 
41
60
  ### Associations
42
61
 
43
- 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:
64
+
65
+ ```ruby
66
+ class Event < ActiveRecord::Base
67
+ extend StaticAssociation::AssociationHelpers
44
68
 
45
- belongs_to_static :day
69
+ belongs_to_static :day
70
+ end
71
+ ```
46
72
 
47
73
  This assumes your model has a field `day_id`.
48
74
 
@@ -51,6 +77,6 @@ This assumes your model has a field `day_id`.
51
77
  1. Fork it
52
78
  2. Create your feature branch (`git checkout -b my-new-feature`)
53
79
  3. Commit your changes (`git commit -am 'Add some feature'`)
54
- 4. Run the tests (`rake`)
80
+ 4. Run lint checks and tests (`bundle exec rake`)
55
81
  5. Push to the branch (`git push origin my-new-feature`)
56
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", "3.2"
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.0"
5
+ gem "activesupport", "7.2"
6
6
 
7
- gemspec :path=>"../"
7
+ gemspec path: "../"
@@ -2,7 +2,6 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "i18n"
6
- gem "activesupport", "3.0"
5
+ gem "activesupport", "8.0"
7
6
 
8
- gemspec :path=>"../"
7
+ gemspec path: "../"
@@ -1,3 +1,3 @@
1
1
  module StaticAssociation
2
- VERSION = "0.0.2"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -1,16 +1,20 @@
1
- require 'static_association/version'
2
- require 'active_support/concern'
3
- require 'active_support/ordered_hash'
4
- require 'active_support/core_ext/module/delegation'
5
- require 'active_support/core_ext/hash/keys'
6
- 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"
7
6
 
8
7
  module StaticAssociation
9
8
  extend ActiveSupport::Concern
10
9
 
10
+ class ArgumentError < StandardError; end
11
+
11
12
  class DuplicateID < StandardError; end
13
+
12
14
  class RecordNotFound < StandardError; end
13
15
 
16
+ class UndefinedAttribute < StandardError; end
17
+
14
18
  attr_reader :id
15
19
 
16
20
  private
@@ -22,44 +26,86 @@ module StaticAssociation
22
26
  module ClassMethods
23
27
  include Enumerable
24
28
 
25
- delegate :each, :to => :all
29
+ delegate :each, to: :all
26
30
 
27
31
  def index
28
- @index ||= ActiveSupport::OrderedHash.new
32
+ @index ||= {}
29
33
  end
30
34
 
31
35
  def all
32
36
  index.values
33
37
  end
34
38
 
39
+ def ids
40
+ index.keys
41
+ end
42
+
35
43
  def find(id)
36
- raise RecordNotFound unless index.has_key?(id)
37
- index[id]
44
+ find_by_id(id) or raise RecordNotFound.new(
45
+ "Couldn't find DummyClass with 'id'=#{id}"
46
+ )
47
+ end
48
+
49
+ def find_by_id(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) }
38
63
  end
39
64
 
40
- def record(settings)
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
+ )
70
+ end
71
+
72
+ def record(settings, &block)
41
73
  settings.assert_valid_keys(:id)
42
74
  id = settings.fetch(:id)
43
- raise DuplicateID if index.has_key?(id)
44
- record = self.new(id)
45
- yield(record) 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
46
82
  index[id] = record
47
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
48
96
  end
49
97
 
50
98
  module AssociationHelpers
51
- def belongs_to_static(name)
52
- self.send(:define_method, name) do
53
- begin
54
- foreign_key = self.send("#{name}_id")
55
- name.to_s.camelize.constantize.find(foreign_key) if foreign_key
56
- rescue RecordNotFound
57
- nil
58
- end
99
+ def belongs_to_static(name, opts = {})
100
+ class_name = opts.fetch(:class_name, name.to_s.camelize)
101
+
102
+ send(:define_method, name) do
103
+ foreign_key = send(:"#{name}_id")
104
+ class_name.constantize.find_by_id(foreign_key)
59
105
  end
60
106
 
61
- self.send(:define_method, "#{name}=") do |assoc|
62
- self.send("#{name}_id=", assoc.id)
107
+ send(:define_method, "#{name}=") do |assoc|
108
+ send(:"#{name}_id=", assoc.id)
63
109
  end
64
110
  end
65
111
  end
data/spec/spec_helper.rb CHANGED
@@ -4,14 +4,10 @@
4
4
  # loaded once.
5
5
  #
6
6
  # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
- require 'rubygems'
8
- require 'bundler/setup'
7
+ require "bundler/setup"
9
8
 
10
- require 'static_association'
9
+ require "static_association"
11
10
 
12
11
  RSpec.configure do |config|
13
- config.treat_symbols_as_metadata_keys_with_true_values = true
14
- config.run_all_when_everything_filtered = true
15
- config.filter_run :focus
16
- config.order = 'random'
12
+ config.order = "random"
17
13
  end
@@ -1,105 +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 |c|
19
- c.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 |c|
28
- c.name = 'asdf'
29
+ context "when using `self`" do
30
+ it "assigns attributes" do
31
+ record = DummyClass.record(id: 1) { self.name = "test" }
32
+
33
+ expect(record.id).to eq(1)
34
+ expect(record.name).to eq("test")
35
+ end
36
+ end
37
+
38
+ context "when using the object" do
39
+ it "assigns attributes" do
40
+ record = DummyClass.record(id: 1) { |i| i.name = "test" }
41
+
42
+ expect(record.id).to eq(1)
43
+ expect(record.name).to eq("test")
44
+ end
45
+ end
46
+
47
+ context "when the id is a duplicate" do
48
+ it "raises an error" do
49
+ DummyClass.record(id: 1) { self.name = "test0" }
50
+
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
57
+ end
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
72
+
73
+ context "without a block" do
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
86
+
87
+ expect(records).to contain_exactly(record1, record2)
88
+ end
89
+ end
90
+
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)
130
+ end
131
+ end
132
+ end
133
+
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
150
+ end
151
+ end
152
+
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")
178
+
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"
29
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
219
+ end
30
220
 
31
- DummyClass.record :id => 1 do |c|
32
- c.name = 'asdf'
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"
33
225
  end
34
- }.to raise_error(StaticAssociation::DuplicateID)
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
35
274
  end
36
- end
37
275
 
38
- context "sets up the instance" do
39
- subject {
40
- DummyClass.record :id => 1 do |c|
41
- c.name = 'asdf'
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
+ )
42
286
  end
43
- }
287
+ end
44
288
 
45
- its(:id) { should == 1 }
46
- its(:name) { should == 'asdf' }
289
+ context "with no attributes" do
290
+ it "raises a StaticAssociation::ArgumentError" do
291
+ expect {
292
+ DummyClass.find_by
293
+ }.to raise_error(StaticAssociation::ArgumentError)
294
+ end
295
+ end
47
296
  end
297
+ end
48
298
 
49
- context "without a block" do
50
- subject { DummyClass.record :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
51
308
 
52
- its(:id) { should == 1 }
53
- its(:name) { should be_nil }
309
+ found_record = DummyClass.find_by!(name: "foo")
310
+
311
+ expect(found_record).to eq(record1)
312
+ end
54
313
  end
55
314
 
56
- context "asserting valid keys" do
57
- it "should raise an error" do
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
+
58
321
  expect {
59
- DummyClass.record :id => 1, :foo => :bar
60
- }.to raise_error(ArgumentError)
322
+ DummyClass.find_by!(name: "bar")
323
+ }.to raise_error(
324
+ StaticAssociation::RecordNotFound,
325
+ "Couldn't find DummyClass with name=bar"
326
+ )
61
327
  end
62
328
  end
63
- end
64
329
 
65
- describe ".find" do
66
- before do
67
- DummyClass.record :id => 1 do |c|
68
- c.name = 'asdf'
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)
69
342
  end
70
343
  end
71
344
 
72
- context "record exists" do
73
- subject { DummyClass.find(1) }
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
74
353
 
75
- it { should be_kind_of(DummyClass) }
76
- its(:id) { should == 1 }
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
77
374
  end
78
375
 
79
- context "record does not exist" do
80
- it "should raise a StaticAssociation::RecordNotFoundError" do
376
+ context "with undefined attributes" do
377
+ it "raises a StaticAssociation::UndefinedAttribute" do
378
+ DummyClass.record(id: 1)
379
+
81
380
  expect {
82
- DummyClass.find(:not_in_the_index)
83
- }.to raise_error(StaticAssociation::RecordNotFound)
381
+ DummyClass.find_by!(undefined_attribute: 1)
382
+ }.to raise_error(
383
+ StaticAssociation::UndefinedAttribute,
384
+ "Undefined attribute 'undefined_attribute'"
385
+ )
386
+ end
387
+ end
388
+
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)
84
394
  end
85
395
  end
86
396
  end
87
397
 
88
398
  describe ".belongs_to_static" do
89
- class AssociationClass
90
- attr_accessor :dummy_class_id
91
-
92
- extend StaticAssociation::AssociationHelpers
93
- belongs_to_static :dummy_class
399
+ it "defines a reader method for the association" do
400
+ associated_class = AssociationClass.new
401
+ allow(DummyClass).to receive(:find_by_id)
402
+
403
+ associated_class.dummy_class
404
+
405
+ expect(DummyClass).to have_received(:find_by_id)
94
406
  end
95
407
 
96
- let(:associated_class) { AssociationClass.new }
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)
97
412
 
98
- it "creates reader method that uses the correct singularized class when finding static association" do
99
- expect {
100
- DummyClass.should_receive(:find)
101
- }
102
- associated_class.dummy_class
413
+ associated_class.dodo_class
414
+
415
+ expect(DummyClass).to have_received(:find_by_id)
416
+ end
103
417
  end
104
418
  end
105
419
  end
@@ -1,26 +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"
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"
14
14
 
15
- spec.files = `git ls-files`.split($/)
16
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
- 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) }
18
17
  spec.require_paths = ["lib"]
19
18
 
20
- spec.add_dependency "activesupport", ">= 3.0.0"
19
+ spec.add_dependency "activesupport", ">= 7.1.0"
21
20
 
22
- spec.add_development_dependency "bundler", "~> 1.3"
23
21
  spec.add_development_dependency "rspec"
24
- spec.add_development_dependency "rake"
25
22
  spec.add_development_dependency "appraisal"
23
+ spec.add_development_dependency "standard"
26
24
  end
metadata CHANGED
@@ -1,83 +1,68 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: static_association
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
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: 2013-10-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
15
14
  requirement: !ruby/object:Gem::Requirement
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
- - - '>='
30
+ - - ">="
46
31
  - !ruby/object:Gem::Version
47
32
  version: '0'
48
33
  type: :development
49
34
  prerelease: false
50
35
  version_requirements: !ruby/object:Gem::Requirement
51
36
  requirements:
52
- - - '>='
37
+ - - ">="
53
38
  - !ruby/object:Gem::Version
54
39
  version: '0'
55
40
  - !ruby/object:Gem::Dependency
56
- name: rake
41
+ name: appraisal
57
42
  requirement: !ruby/object:Gem::Requirement
58
43
  requirements:
59
- - - '>='
44
+ - - ">="
60
45
  - !ruby/object:Gem::Version
61
46
  version: '0'
62
47
  type: :development
63
48
  prerelease: false
64
49
  version_requirements: !ruby/object:Gem::Requirement
65
50
  requirements:
66
- - - '>='
51
+ - - ">="
67
52
  - !ruby/object:Gem::Version
68
53
  version: '0'
69
54
  - !ruby/object:Gem::Dependency
70
- name: appraisal
55
+ name: standard
71
56
  requirement: !ruby/object:Gem::Requirement
72
57
  requirements:
73
- - - '>='
58
+ - - ">="
74
59
  - !ruby/object:Gem::Version
75
60
  version: '0'
76
61
  type: :development
77
62
  prerelease: false
78
63
  version_requirements: !ruby/object:Gem::Requirement
79
64
  requirements:
80
- - - '>='
65
+ - - ">="
81
66
  - !ruby/object:Gem::Version
82
67
  version: '0'
83
68
  description: StaticAssociation adds a simple enum type that can act like an ActiveRecord
@@ -88,50 +73,44 @@ executables: []
88
73
  extensions: []
89
74
  extra_rdoc_files: []
90
75
  files:
91
- - .gitignore
92
- - .travis.yml
76
+ - ".github/workflows/lint.yml"
77
+ - ".github/workflows/test.yml"
78
+ - ".gitignore"
79
+ - ".standard.yml"
93
80
  - Appraisals
81
+ - CHANGELOG.md
82
+ - CODEOWNERS
94
83
  - Gemfile
95
84
  - LICENSE.txt
96
85
  - README.md
97
86
  - Rakefile
98
- - gemfiles/3.0.gemfile
99
- - gemfiles/3.0.gemfile.lock
100
- - gemfiles/3.1.gemfile
101
- - gemfiles/3.1.gemfile.lock
102
- - gemfiles/3.2.gemfile
103
- - gemfiles/3.2.gemfile.lock
104
- - gemfiles/4.0.gemfile
105
- - gemfiles/4.0.gemfile.lock
87
+ - gemfiles/7.1.gemfile
88
+ - gemfiles/7.2.gemfile
89
+ - gemfiles/8.0.gemfile
106
90
  - lib/static_association.rb
107
91
  - lib/static_association/version.rb
108
92
  - spec/spec_helper.rb
109
93
  - spec/static_association_spec.rb
110
94
  - static_association.gemspec
111
- homepage:
95
+ homepage: https://github.com/thoughtbot/static_association
112
96
  licenses:
113
97
  - MIT
114
98
  metadata: {}
115
- post_install_message:
116
99
  rdoc_options: []
117
100
  require_paths:
118
101
  - lib
119
102
  required_ruby_version: !ruby/object:Gem::Requirement
120
103
  requirements:
121
- - - '>='
104
+ - - ">="
122
105
  - !ruby/object:Gem::Version
123
106
  version: '0'
124
107
  required_rubygems_version: !ruby/object:Gem::Requirement
125
108
  requirements:
126
- - - '>='
109
+ - - ">="
127
110
  - !ruby/object:Gem::Version
128
111
  version: '0'
129
112
  requirements: []
130
- rubyforge_project:
131
- rubygems_version: 2.0.3
132
- signing_key:
113
+ rubygems_version: 3.6.9
133
114
  specification_version: 4
134
115
  summary: ActiveRecord like associations for static data
135
- test_files:
136
- - spec/spec_helper.rb
137
- - spec/static_association_spec.rb
116
+ test_files: []
data/.travis.yml DELETED
@@ -1,20 +0,0 @@
1
- language: ruby
2
- install: bundle install
3
- script:
4
- - bundle exec rake
5
- rvm:
6
- - 2.0.0
7
- - 1.9.3
8
- - 1.9.2
9
- - 1.8.7
10
- gemfile:
11
- - gemfiles/3.0.gemfile
12
- - gemfiles/3.1.gemfile
13
- - gemfiles/3.2.gemfile
14
- - gemfiles/4.0.gemfile
15
- matrix:
16
- exclude:
17
- - rvm: 1.8.7
18
- gemfile: gemfiles/4.0.gemfile
19
- - rvm: 1.9.2
20
- gemfile: gemfiles/4.0.gemfile
@@ -1,36 +0,0 @@
1
- PATH
2
- remote: /Users/olivernightingale/code/static_association
3
- specs:
4
- static_association (0.0.1)
5
- activesupport (>= 3.0.0)
6
-
7
- GEM
8
- remote: https://rubygems.org/
9
- specs:
10
- activesupport (3.0.0)
11
- appraisal (0.5.2)
12
- bundler
13
- rake
14
- diff-lcs (1.2.4)
15
- i18n (0.6.5)
16
- rake (10.1.0)
17
- rspec (2.14.1)
18
- rspec-core (~> 2.14.0)
19
- rspec-expectations (~> 2.14.0)
20
- rspec-mocks (~> 2.14.0)
21
- rspec-core (2.14.5)
22
- rspec-expectations (2.14.3)
23
- diff-lcs (>= 1.1.3, < 2.0)
24
- rspec-mocks (2.14.3)
25
-
26
- PLATFORMS
27
- ruby
28
-
29
- DEPENDENCIES
30
- activesupport (= 3.0)
31
- appraisal
32
- bundler (~> 1.3)
33
- i18n
34
- rake
35
- rspec
36
- static_association!
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=>"../"
@@ -1,38 +0,0 @@
1
- PATH
2
- remote: /Users/olivernightingale/code/static_association
3
- specs:
4
- static_association (0.0.1)
5
- activesupport (>= 3.0.0)
6
-
7
- GEM
8
- remote: https://rubygems.org/
9
- specs:
10
- activesupport (3.1.0)
11
- multi_json (~> 1.0)
12
- appraisal (0.5.2)
13
- bundler
14
- rake
15
- diff-lcs (1.2.4)
16
- i18n (0.6.5)
17
- multi_json (1.8.0)
18
- rake (10.1.0)
19
- rspec (2.14.1)
20
- rspec-core (~> 2.14.0)
21
- rspec-expectations (~> 2.14.0)
22
- rspec-mocks (~> 2.14.0)
23
- rspec-core (2.14.5)
24
- rspec-expectations (2.14.3)
25
- diff-lcs (>= 1.1.3, < 2.0)
26
- rspec-mocks (2.14.3)
27
-
28
- PLATFORMS
29
- ruby
30
-
31
- DEPENDENCIES
32
- activesupport (= 3.1)
33
- appraisal
34
- bundler (~> 1.3)
35
- i18n
36
- rake
37
- rspec
38
- static_association!
@@ -1,38 +0,0 @@
1
- PATH
2
- remote: /Users/olivernightingale/code/static_association
3
- specs:
4
- static_association (0.0.1)
5
- activesupport (>= 3.0.0)
6
-
7
- GEM
8
- remote: https://rubygems.org/
9
- specs:
10
- activesupport (3.2.0)
11
- i18n (~> 0.6)
12
- multi_json (~> 1.0)
13
- appraisal (0.5.2)
14
- bundler
15
- rake
16
- diff-lcs (1.2.4)
17
- i18n (0.6.5)
18
- multi_json (1.8.0)
19
- rake (10.1.0)
20
- rspec (2.14.1)
21
- rspec-core (~> 2.14.0)
22
- rspec-expectations (~> 2.14.0)
23
- rspec-mocks (~> 2.14.0)
24
- rspec-core (2.14.5)
25
- rspec-expectations (2.14.3)
26
- diff-lcs (>= 1.1.3, < 2.0)
27
- rspec-mocks (2.14.3)
28
-
29
- PLATFORMS
30
- ruby
31
-
32
- DEPENDENCIES
33
- activesupport (= 3.2)
34
- appraisal
35
- bundler (~> 1.3)
36
- rake
37
- rspec
38
- static_association!
@@ -1,46 +0,0 @@
1
- PATH
2
- remote: /Users/olivernightingale/code/static_association
3
- specs:
4
- static_association (0.0.1)
5
- activesupport (>= 3.0.0)
6
-
7
- GEM
8
- remote: https://rubygems.org/
9
- specs:
10
- activesupport (4.0.0)
11
- i18n (~> 0.6, >= 0.6.4)
12
- minitest (~> 4.2)
13
- multi_json (~> 1.3)
14
- thread_safe (~> 0.1)
15
- tzinfo (~> 0.3.37)
16
- appraisal (0.5.2)
17
- bundler
18
- rake
19
- atomic (1.1.13)
20
- diff-lcs (1.2.4)
21
- i18n (0.6.5)
22
- minitest (4.7.5)
23
- multi_json (1.8.0)
24
- rake (10.1.0)
25
- rspec (2.14.1)
26
- rspec-core (~> 2.14.0)
27
- rspec-expectations (~> 2.14.0)
28
- rspec-mocks (~> 2.14.0)
29
- rspec-core (2.14.5)
30
- rspec-expectations (2.14.3)
31
- diff-lcs (>= 1.1.3, < 2.0)
32
- rspec-mocks (2.14.3)
33
- thread_safe (0.1.2)
34
- atomic
35
- tzinfo (0.3.37)
36
-
37
- PLATFORMS
38
- ruby
39
-
40
- DEPENDENCIES
41
- activesupport (= 4.0)
42
- appraisal
43
- bundler (~> 1.3)
44
- rake
45
- rspec
46
- static_association!