external_fields 0.1.2 → 0.1.3

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
2
  SHA256:
3
- metadata.gz: 86d24f2850207b76eb4914b60a3460b74647e9fbfbb0ec3acee1aa6b4acdd492
4
- data.tar.gz: d32ff8c9edc73dae34c0ddb0982b818eb23004a73be8718acbe55b5ddb9ca1be
3
+ metadata.gz: 375c47faf50c7a1c7b540a6e410943b330b91c7b2f6298de6f4e5e5d43395428
4
+ data.tar.gz: 59008d089e1374dcd68369dab0268679a4a7fab016017f5c680ac42ceb95d6d9
5
5
  SHA512:
6
- metadata.gz: 3fe1e749cdd7df405692ab8b8023e24af452f35442541ed844b5938144fc5db23e65e16d28b2e372b7a6c3c06ace3495a0354c33710f01978933c32b18cfdd30
7
- data.tar.gz: d2e0cfbfad48b26cbc8da4a445d98ada47d4579f325478ca15c539fc1df818f9ea4701d9c9488f7aa5c9da738b5969f89795824d0aef380561f736737eb64124
6
+ metadata.gz: d0e16175a6da46dd1f70725f7078d2ea73b98f6d32357cd063da2cee94190721cff76b35b89a5d58b75ac0a140d88a80e954905165e43380379250e761994c10
7
+ data.tar.gz: fb3e808122d1042f97763f04bfe255681000327c910adc52a6c1b51243c40ae2ba063bfcc3d2e5486f6f6c0dd984f4210f80a637f9d75aaadde81e74a3903b5f
@@ -0,0 +1,47 @@
1
+ # This is a dependabot configuration file. When this file is seen on github, a
2
+ # dependabot configuration is created for the project. We can use this to
3
+ # control various aspects of the automated dependency checking, such as the
4
+ # frequency and the target_branch.
5
+ #
6
+ # Reference: https://dependabot.com/docs/config-file/
7
+ version: 1
8
+ update_configs:
9
+ # This configures dependency updates for one package manager. In some
10
+ # projects, such as warehouse, where we have Ruby and Python, there can be
11
+ # separate package_manager entries.
12
+ - package_manager: "ruby:bundler"
13
+ directory: "/"
14
+ update_schedule: "weekly"
15
+
16
+ default_labels:
17
+ - "dependencies"
18
+ - "Needs QA"
19
+
20
+ # Dependabot will use a repository's default branch. This will override
21
+ # that.
22
+ # target_branch: "master"
23
+
24
+ allowed_updates:
25
+ - match:
26
+ dependency_type: "direct"
27
+
28
+ automerged_updates:
29
+ # This allows all dependencies that are used for development, e.g., rspec,
30
+ # rspec-mock, vcr, etc, to be automatically updated. This is generally
31
+ # okay because the dependencies are not used in production.
32
+ - match:
33
+ dependency_type: "development"
34
+ update_type: "all"
35
+
36
+ # # This is an example entry to enable automerging of a specific dependency
37
+ # # when the update is only for minor or patch semantic versions.
38
+ # #
39
+ # # The dependency_name can also be a wildcard.
40
+ # #
41
+ # # This is left commented, but whitelisting a dependency for automatic
42
+ # # merging is as simple as creating a new entry that looks like the below.
43
+ # - match:
44
+ # dependency_type: "all"
45
+ # dependency_name: "aws-sdk-s3"
46
+ # update_type: "semver:minor"
47
+
@@ -0,0 +1,26 @@
1
+ # This workflow auto-approves pull-requests when the github actor is a
2
+ # dependabot user and it is opening a new pull request.
3
+ #
4
+ # The problem that this workflow solves is that we have branch protection on
5
+ # our repositories that prevent PRs from merging unless there is an
6
+ # approval present. The problem is that this will block PRs that dependabot
7
+ # may want to merge automatically. Auto-approving dependabot PRs will allow
8
+ # the automatic merge to complete. We control what gets automerged through
9
+ # the dependabot configuration.
10
+ #
11
+ # This is a known issue: https://github.com/dependabot/feedback/issues/852
12
+ name: Auto-approve dependabot pull requests
13
+ on:
14
+ pull_request:
15
+ types: [opened]
16
+
17
+ jobs:
18
+ dependabot-triage:
19
+ runs-on: ubuntu-latest
20
+ if: (github.actor == 'dependabot[bot]' || github.actor == 'dependabot-preview[bot]')
21
+
22
+ steps:
23
+ - name: Auto-approve for dependabot
24
+ uses: hmarr/auto-approve-action@v2.0.0
25
+ with:
26
+ github-token: "${{ secrets.GITHUB_TOKEN }}"
@@ -0,0 +1,35 @@
1
+ # This workflow removes a "Needs QA" label from a PR when the actor is the
2
+ # dependabot user merging a PR.
3
+ #
4
+ # We need this mechanism to allow for automerging whitelisted dependencies while
5
+ # also allowing for blocking a merge to master for deployment (in the way that
6
+ # our other PRs work). When the automerge script runs in henchman, it looks
7
+ # for `Needs QA` on github pull requests, and if the label is present,
8
+ # blocks the commit from merging.
9
+ name: Remove 'Needs QA' label for auto-merged PRs.
10
+ on:
11
+ pull_request:
12
+ types: [closed]
13
+
14
+ jobs:
15
+ remove-label:
16
+ runs-on: ubuntu-latest
17
+ if: >
18
+ (github.actor == 'dependabot[bot]' || github.actor == 'dependabot-preview[bot]')
19
+ && github.event.pull_request.merged
20
+
21
+ steps:
22
+ # Our triage workflow adds 'Needs QA' to the PR in order to block it from
23
+ # merging to production. This removes that label when dependabot is doing
24
+ # the merging.
25
+ - name: Remove QA Label
26
+ uses: actions/github-script@0.4.0
27
+ with:
28
+ github-token: ${{ secrets.GITHUB_TOKEN }}
29
+ script: |
30
+ github.issues.removeLabel({
31
+ issue_number: context.issue.number,
32
+ owner: context.repo.owner,
33
+ repo: context.repo.repo,
34
+ name: 'Needs QA'
35
+ })
@@ -0,0 +1,33 @@
1
+ # based on https://github.com/ruby/setup-ruby/blob/master/README.md
2
+ name: Tests
3
+ on: [push, pull_request]
4
+ jobs:
5
+ ci:
6
+ name: CI
7
+ strategy:
8
+ fail-fast: false
9
+ matrix:
10
+ os: [ ubuntu-latest ]
11
+ ruby: [ 2.5, 2.6, 2.7 ]
12
+ runs-on: ${{ matrix.os }}
13
+ env:
14
+ CI: true
15
+ steps:
16
+ - uses: actions/checkout@v2
17
+ - uses: ruby/setup-ruby@v1
18
+ with:
19
+ ruby-version: ${{ matrix.ruby }}
20
+ - uses: actions/cache@v1
21
+ with:
22
+ path: vendor/bundle
23
+ key: bundle-use-ruby-${{ matrix.os }}-${{ matrix.ruby }}-${{ hashFiles('**/Gemfile.lock') }}
24
+ restore-keys: |
25
+ bundle-use-ruby-${{ matrix.os }}-${{ matrix.ruby }}-
26
+ - run: sudo apt-get install libsqlite3-dev
27
+ - name: bundle install
28
+ run: |
29
+ ruby -v
30
+ bundle config path vendor/bundle
31
+ bundle install --jobs 4 --retry 3
32
+ - run: bundle exec rubocop
33
+ - run: bundle exec rspec
data/.rubocop.yml CHANGED
@@ -1,271 +1,2 @@
1
- require:
2
- - rubocop/rspec/focused
3
-
4
- # This (http://c2.com/cgi/wiki?AbcMetric) is super obnoxious
5
- AbcSize:
6
- Enabled: false
7
-
8
- AccessorMethodName:
9
- Enabled: false
10
-
11
- Alias:
12
- Enabled: false
13
-
14
- AllCops:
15
- Exclude:
16
- - "vendor/**/*"
17
- - "spec/dummy/**/*"
18
- - "db/schema.rb"
19
- - "db/migrate/**/*"
20
- RunRailsCops: true
21
-
22
- AmbiguousOperator:
23
- Enabled: false
24
-
25
- AmbiguousRegexpLiteral:
26
- Enabled: false
27
-
28
- ArrayJoin:
29
- Enabled: false
30
-
31
- AsciiComments:
32
- Enabled: false
33
-
34
- AsciiIdentifiers:
35
- Enabled: false
36
-
37
- AssignmentInCondition:
38
- Enabled: true
39
-
40
- Attr:
41
- Enabled: false
42
-
43
- BlockNesting:
44
- Enabled: false
45
-
46
- BracesAroundHashParameters:
47
- Enabled: false
48
-
49
- CaseEquality:
50
- Enabled: false
51
-
52
- CharacterLiteral:
53
- Enabled: false
54
-
55
- ClassLength:
56
- Enabled: false
57
-
58
- ClassVars:
59
- Enabled: false
60
-
61
- CollectionMethods:
62
- PreferredMethods:
63
- find: detect
64
- reduce: inject
65
- collect: map
66
- find_all: select
67
-
68
- ColonMethodCall:
69
- Enabled: false
70
-
71
- CommentAnnotation:
72
- Enabled: false
73
-
74
- ConditionPosition:
75
- Enabled: false
76
-
77
- CyclomaticComplexity:
78
- Enabled: false
79
-
80
- Delegate:
81
- Enabled: false
82
-
83
- DeprecatedClassMethods:
84
- Enabled: false
85
-
86
- DeprecatedHashMethods:
87
- Enabled: false
88
-
89
- Documentation:
90
- Enabled: false
91
-
92
- DotPosition:
93
- EnforcedStyle: trailing
94
-
95
- DoubleNegation:
96
- Enabled: false
97
-
98
- ElseLayout:
99
- Enabled: false
100
-
101
- EmptyLiteral:
102
- Enabled: false
103
-
104
- Encoding:
105
- Enabled: false
106
-
107
- EvenOdd:
108
- Enabled: false
109
-
110
- FileName:
111
- Enabled: false
112
-
113
- FlipFlop:
114
- Enabled: false
115
-
116
- FormatString:
117
- Enabled: false
118
-
119
- GlobalVars:
120
- Enabled: false
121
-
122
- GuardClause:
123
- Enabled: false
124
-
125
- HandleExceptions:
126
- Enabled: false
127
-
128
- IfUnlessModifier:
129
- Enabled: false
130
-
131
- IfWithSemicolon:
132
- Enabled: false
133
-
134
- InvalidCharacterLiteral:
135
- Enabled: false
136
-
137
- Lambda:
138
- Enabled: false
139
-
140
- LambdaCall:
141
- Enabled: false
142
-
143
- LineEndConcatenation:
144
- Enabled: false
145
-
146
- LineLength:
147
- Max: 80
148
-
149
- LiteralInCondition:
150
- Enabled: false
151
-
152
- LiteralInInterpolation:
153
- Enabled: false
154
-
155
- Loop:
156
- Enabled: false
157
-
158
- MethodLength:
159
- Enabled: false
160
-
161
- ModuleFunction:
162
- Enabled: false
163
-
164
- NegatedIf:
165
- Enabled: false
166
-
167
- NegatedWhile:
168
- Enabled: false
169
-
170
- Next:
171
- Enabled: false
172
-
173
- NilComparison:
174
- Enabled: false
175
-
176
- Not:
177
- Enabled: false
178
-
179
- NumericLiterals:
180
- Enabled: false
181
-
182
- OneLineConditional:
183
- Enabled: false
184
-
185
- OpMethod:
186
- Enabled: false
187
-
188
- ParameterLists:
189
- Enabled: false
190
-
191
- ParenthesesAsGroupedExpression:
192
- Enabled: false
193
-
194
- PercentLiteralDelimiters:
195
- PreferredDelimiters:
196
- '%': '{}'
197
-
198
- PerceivedComplexity:
199
- Enabled: false
200
-
201
- PerlBackrefs:
202
- Enabled: false
203
-
204
- PredicateName:
205
- Enabled: false
206
-
207
- Proc:
208
- Enabled: false
209
-
210
- RaiseArgs:
211
- Enabled: false
212
-
213
- RedundantReturn:
214
- AllowMultipleReturnValues: true
215
-
216
- RegexpLiteral:
217
- Enabled: false
218
-
219
- RequireParentheses:
220
- Enabled: false
221
-
222
- Rspec/Focused:
223
- Enabled: true
224
-
225
- SelfAssignment:
226
- Enabled: false
227
-
228
- SignalException:
229
- EnforcedStyle: only_raise
230
-
231
- SingleLineBlockParams:
232
- Enabled: false
233
-
234
- SingleLineMethods:
235
- Enabled: false
236
-
237
- SpecialGlobalVars:
238
- Enabled: false
239
-
240
- StringLiterals:
241
- EnforcedStyle: double_quotes
242
-
243
- Style/MultilineBlockChain:
244
- Enabled: false
245
-
246
- VariableInterpolation:
247
- Enabled: false
248
-
249
- TrailingComma:
250
- Enabled: false
251
-
252
- TrivialAccessors:
253
- Enabled: false
254
-
255
- UnderscorePrefixedVariableName:
256
- Enabled: false
257
-
258
- VariableInterpolation:
259
- Enabled: false
260
-
261
- Void:
262
- Enabled: false
263
-
264
- WhenThen:
265
- Enabled: false
266
-
267
- WhileUntilModifier:
268
- Enabled: false
269
-
270
- WordArray:
271
- Enabled: false
1
+ inherit_gem:
2
+ panolint: rubocop.yml
data/CHANGELOG.md ADDED
@@ -0,0 +1,14 @@
1
+ # 0.1.3
2
+
3
+ - Drop official support for Ruby 2.3 and below, and Rails 4, though these
4
+ versions may continue to work.
5
+ - Add optional `save_empty` parameter for external fields, making it possible to
6
+ access un-saved empty associated records.
7
+
8
+ # 0.1.2
9
+
10
+ Add support for Rails 5 [(#12)[https://github.com/panorama-ed/rails_external_fields/pull/12]]
11
+
12
+ # 0.1.1
13
+
14
+ The initial release! All basic functionality is in here.
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at engineering@panoramaed.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [https://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: https://contributor-covenant.org
74
+ [version]: https://contributor-covenant.org/version/1/4/
data/Gemfile CHANGED
@@ -1,4 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source "https://rubygems.org"
2
4
 
3
- # Specify your gem's dependencies in external_fields.gemspec
5
+ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
6
+
4
7
  gemspec
8
+
9
+ group :development do
10
+ gem "panolint", github: "panorama-ed/panolint", branch: "main"
11
+ end
data/README.md CHANGED
@@ -1,4 +1,7 @@
1
- [![Code Climate](https://codeclimate.com/github/panorama-ed/rails_external_fields/badges/gpa.svg)](https://codeclimate.com/github/panorama-ed/rails_external_fields) [![Test Coverage](https://codeclimate.com/github/panorama-ed/rails_external_fields/badges/coverage.svg)](https://codeclimate.com/github/panorama-ed/rails_external_fields) [![Build Status](https://travis-ci.org/panorama-ed/rails_external_fields.svg)](https://travis-ci.org/panorama-ed/rails_external_fields) [![Inline docs](http://inch-ci.org/github/panorama-ed/rails_external_fields.png)](http://inch-ci.org/github/panorama-ed/rails_external_fields) [![Gem Version](https://badge.fury.io/rb/external_fields.svg)](http://badge.fury.io/rb/external_fields)
1
+ [![Code Coverage](https://codecov.io/gh/panorama-ed/rails_external_fields/branch/main/graph/badge.svg)](https://codecov.io/gh/panorama-ed/rails_external_fields)
2
+ [![Build Status](https://travis-ci.com/panorama-ed/rails_external_fields.svg)](https://travis-ci.com/panorama-ed/rails_external_fields)
3
+ [![Inline docs](http://inch-ci.org/github/panorama-ed/rails_external_fields.png)](http://inch-ci.org/github/panorama-ed/rails_external_fields)
4
+ [![Gem Version](https://badge.fury.io/rb/external_fields.svg)](http://badge.fury.io/rb/external_fields)
2
5
 
3
6
  # ExternalFields
4
7
  Create the illusion that an object has specific attributes when those attributes
@@ -42,11 +45,12 @@ class Student < ActiveRecord::Base
42
45
  has_one :data,
43
46
  class_name: StudentData
44
47
 
45
- external_field :grade_level, # External attribute 1
46
- :age, # External attribute 2
47
- :credits, # External attribute 3
48
- :data, # Name of the association
49
- class_name: "StudentData" # Class name of association
48
+ external_field :grade_level, # External attribute 1
49
+ :age, # External attribute 2
50
+ :credits, # External attribute 3
51
+ :data, # Name of the association
52
+ class_name: "StudentData", # Class name of association
53
+ save_empty: false # Don't save empty associations
50
54
  end
51
55
  ```
52
56
 
@@ -108,6 +112,31 @@ def grade_level
108
112
  end
109
113
  ```
110
114
 
115
+ ### Overriding default behavior using `save_empty: false`
116
+ **This is the recommended configuration to use for all new code.**
117
+
118
+ To avoid unnecessary writes, you can rely on empty-valued class instances so
119
+ that external associations are only saved when they have one or more attributes
120
+ with non-default values.
121
+
122
+ For any given association class, its constructor defines the attribute values
123
+ for an "empty" instance. This means that, in the below example, retreival of
124
+ `data` will return `StudentData.new` if there's no `StudentData` record saved.
125
+ If `set_empty: true` were configured instead, calling `data` would still return
126
+ `StudentData.new`, but it would also write the empty record to the database.
127
+
128
+ ```ruby
129
+ external_field :grade_level, # External attribute 1
130
+ :age, # External attribute 2
131
+ :credits, # External attribute 3
132
+ :data, # Name of the association
133
+ class_name: "StudentData", # Class name of association
134
+ save_empty: false # Don't save empty associations
135
+ ```
136
+
137
+ The default value for `save_empty` is `true` only for backward compatability,
138
+ as existing code using this gem may rely on empty rows existing in a database.
139
+
111
140
  ### Accessing the original association
112
141
 
113
142
  In some instances it's helpful to be able to use the original association
@@ -132,7 +161,7 @@ end
132
161
 
133
162
  ## Documentation
134
163
 
135
- We have documentation on [RubyDoc](http://www.rubydoc.info/github/panorama-ed/rails_external_fields/master).
164
+ We have documentation on [RubyDoc](http://www.rubydoc.info/github/panorama-ed/rails_external_fields/main).
136
165
 
137
166
  ## Contributing
138
167
 
@@ -149,4 +178,4 @@ and conform to the Rubocop style specified.** We use
149
178
  ## License
150
179
 
151
180
  `ExternalFields` is released under the
152
- [MIT License](https://github.com/panorama-ed/rails_external_fields/blob/master/LICENSE).
181
+ [MIT License](https://github.com/panorama-ed/rails_external_fields/blob/main/LICENSE).
@@ -1,9 +1,10 @@
1
- # coding: utf-8
2
- lib = File.expand_path("../lib", __FILE__)
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path("lib", __dir__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
5
  require "external_fields/version"
5
6
 
6
- Gem::Specification.new do |spec|
7
+ Gem::Specification.new do |spec| # rubocop:disable Metrics/BlockLength
7
8
  spec.name = "external_fields"
8
9
  spec.version = ExternalFields::VERSION
9
10
  spec.authors = ["Sagar Jauhari"]
@@ -26,14 +27,12 @@ Gem::Specification.new do |spec|
26
27
  spec.require_paths = ["lib"]
27
28
 
28
29
  spec.add_dependency "activerecord", ">= 4.0"
30
+ spec.add_dependency "activesupport", ">= 4.0"
29
31
 
30
- spec.add_development_dependency "bundler", "~> 1.7"
31
- spec.add_development_dependency "codeclimate-test-reporter", "~> 0.4"
32
- spec.add_development_dependency "overcommit", "~> 0.23"
33
- spec.add_development_dependency "rspec", "~> 3.2"
34
- spec.add_development_dependency "rspec-rails", "~> 3.2"
35
- spec.add_development_dependency "rubocop", "~> 0.49"
36
- spec.add_development_dependency "rubocop-rspec-focused", "~> 0.0"
37
- spec.add_development_dependency "temping", "~> 3.2"
38
- spec.add_development_dependency "sqlite3", "~> 1.3"
32
+ spec.add_development_dependency "bundler"
33
+ spec.add_development_dependency "codecov"
34
+ spec.add_development_dependency "rspec"
35
+ spec.add_development_dependency "rspec-rails"
36
+ spec.add_development_dependency "sqlite3"
37
+ spec.add_development_dependency "temping"
39
38
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ExternalFields
2
- VERSION = "0.1.2"
4
+ VERSION = "0.1.3"
3
5
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "external_fields/version"
2
4
 
3
5
  # This concern maintains the illusion that a given object has specified
@@ -9,7 +11,7 @@ require "external_fields/version"
9
11
  module ExternalFields
10
12
  extend ActiveSupport::Concern
11
13
 
12
- included do
14
+ included do # rubocop:disable Metrics/BlockLength
13
15
  class_attribute :_external_field_associations
14
16
 
15
17
  # Provides a getter and setter for the given attribute on the associated
@@ -21,10 +23,19 @@ module ExternalFields
21
23
  # @param assoc [Symbol] name of the association
22
24
  # @param class_name [String] name of the associated class
23
25
  # @param underscore [Boolean] underscored accessor created if true
24
- def self.external_field(*attrs, assoc, class_name: nil, underscore: false)
26
+ # @param save_empty [Boolean] Specifies if empty values should be saved.
27
+ # False is recommended, but true is the current
28
+ # default to support backwards compatibility.
29
+ def self.external_field( # rubocop:disable Metrics/PerceivedComplexity
30
+ *attrs,
31
+ assoc,
32
+ class_name: nil,
33
+ underscore: false,
34
+ save_empty: true
35
+ )
25
36
  self._external_field_associations ||= []
26
37
 
27
- attrs.each do |attr|
38
+ attrs.each do |attr| # rubocop:disable Metrics/BlockLength
28
39
  # Store the original association method for use in the overwritten one.
29
40
  original_method = instance_method(assoc)
30
41
 
@@ -35,23 +46,19 @@ module ExternalFields
35
46
  # object if one does not exist already.
36
47
  unless self._external_field_associations.include? assoc
37
48
  define_method assoc do |use_original: false|
38
- if use_original
39
- # Call original overwritten method
40
- original_method.bind(self).call
41
- else
42
- # Try calling the original method to see if we get a result.
43
- existing_value = original_method.bind(self).call
44
-
45
- # Use existing value if one is there.
46
- if existing_value
47
- existing_value
48
- else # Otherwise, build a new object.
49
- # Find the class of the object we need to build.
50
- klass = class_name.try(:constantize) ||
51
- self.class.reflect_on_association(assoc).klass
52
-
53
- send("#{assoc}=", klass.new)
54
- end
49
+ # Call original overwritten method
50
+ existing_value = original_method.bind(self).call
51
+
52
+ # Use existing value if one is there
53
+ if use_original || existing_value
54
+ existing_value
55
+ else # Otherwise, use an empty value
56
+ empty = (
57
+ class_name.try(:constantize) ||
58
+ self.class.reflect_on_association(assoc).klass
59
+ ).new
60
+
61
+ save_empty ? send("#{assoc}=", empty) : empty
55
62
  end
56
63
  end
57
64
  end
@@ -63,7 +70,25 @@ module ExternalFields
63
70
 
64
71
  # Now, define the setters for the specific attribute.
65
72
  define_method(underscore ? "_#{attr}=" : "#{attr}=") do |new_attr|
66
- send(assoc).send("#{attr}=", new_attr)
73
+ default = (
74
+ class_name.try(:constantize) ||
75
+ self.class.reflect_on_association(assoc).klass
76
+ ).new
77
+
78
+ assoc_record = send(assoc)
79
+ if save_empty || (
80
+ !assoc_record.nil? &&
81
+ assoc_record.attributes != default.attributes
82
+ )
83
+ assoc_record.send("#{attr}=", new_attr)
84
+ elsif new_attr != default.send(attr)
85
+ send(
86
+ "#{assoc}=",
87
+ default.tap { |x| x.send("#{attr}=", new_attr) }
88
+ )
89
+ end
90
+
91
+ new_attr
67
92
  end
68
93
 
69
94
  # Add the association name to the set of external field associations.
@@ -72,15 +97,15 @@ module ExternalFields
72
97
  # association name symbols, like: [:address, :extra_data]
73
98
  # Note that a Set could be used here but an Array was chosen for
74
99
  # familiarity since the size of the array will be relatively small.
75
- unless self._external_field_associations.include? assoc
76
- # We need to duplicate the array because a subclass of a model with
77
- # this mixin would otherwise modify its parent class' array, since the
78
- # << operator works in-place.
79
- self._external_field_associations =
80
- self._external_field_associations.dup
100
+ next if self._external_field_associations.include? assoc
81
101
 
82
- self._external_field_associations << assoc
83
- end
102
+ # We need to duplicate the array because a subclass of a model with
103
+ # this mixin would otherwise modify its parent class' array, since the
104
+ # << operator works in-place.
105
+ self._external_field_associations =
106
+ self._external_field_associations.dup
107
+
108
+ self._external_field_associations << assoc
84
109
  end
85
110
  end
86
111
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "spec_helper"
2
4
 
3
5
  RSpec.describe ExternalFields do
@@ -8,26 +10,43 @@ RSpec.describe ExternalFields do
8
10
  t.string :name
9
11
  end
10
12
 
11
- include ExternalFields
13
+ include ExternalFields # rubocop:disable RSpec/DescribedClass
12
14
 
13
15
  has_one :assoc,
14
16
  class_name: "AssociationTestClass"
15
17
 
16
- external_field :ext_field_1,
18
+ has_one :no_empties_assoc,
19
+ class_name: "NoEmptiesAssociationTestClass"
20
+
21
+ external_field :ext_field,
17
22
  :assoc,
18
23
  class_name: "AssociationTestClass"
19
24
 
20
- external_field :ext_field_2,
25
+ external_field :ext_field_using_underscore,
21
26
  :assoc,
22
27
  class_name: "AssociationTestClass",
23
28
  underscore: true
29
+
30
+ external_field :ext_field_using_empties_not_saved,
31
+ :no_empties_assoc,
32
+ class_name: "NoEmptiesAssociationTestClass",
33
+ save_empty: false
24
34
  end
25
35
 
26
36
  Temping.create :association_test_class do
27
37
  with_columns do |t|
28
38
  t.integer :test_class_id
29
- t.string :ext_field_1
30
- t.string :ext_field_2
39
+ t.string :ext_field
40
+ t.string :ext_field_using_underscore
41
+ end
42
+
43
+ belongs_to :test_class
44
+ end
45
+
46
+ Temping.create :no_empties_association_test_class do
47
+ with_columns do |t|
48
+ t.integer :test_class_id
49
+ t.string :ext_field_using_empties_not_saved
31
50
  end
32
51
 
33
52
  belongs_to :test_class
@@ -38,52 +57,110 @@ RSpec.describe ExternalFields do
38
57
  after :each do
39
58
  TestClass.delete_all
40
59
  AssociationTestClass.delete_all
60
+ NoEmptiesAssociationTestClass.delete_all
41
61
  end
42
62
 
43
- it "should be built on first access" do
44
- e = TestClass.create!(name: "Hello")
45
-
63
+ it "is not created or saved if unused" do
64
+ e = TestClass.create!
65
+ e.name = "TEST"
66
+ e.save!
46
67
  expect(AssociationTestClass.count).to eq(0)
47
- expect(e.assoc.class).to eq(AssociationTestClass)
48
68
  end
49
69
 
50
- it "should be saved when the model is saved" do
51
- e = TestClass.create!(name: "Hello")
52
- expect(AssociationTestClass.count).to eq(0)
53
- expect(e.assoc.class).to eq(AssociationTestClass)
70
+ it "provides an accessor that does not build a new object" do
71
+ e = TestClass.new(name: "Hello")
72
+
73
+ e.assoc(use_original: true) # Access without creating
54
74
  e.save!
55
- expect(AssociationTestClass.count).to eq(1)
75
+ expect(AssociationTestClass.count).to eq 0
56
76
  end
57
77
 
58
- it "should not be created or saved if unused" do
59
- e = TestClass.create!
60
- e.name = "TEST"
61
- e.save!
62
- expect(AssociationTestClass.count).to eq(0)
78
+ shared_examples_for "A model with setters" do |external_field|
79
+ it "returns the new value on assignment" do
80
+ new_value = rand.to_s
81
+ expect(
82
+ TestClass.create!.send("#{external_field}=", new_value)
83
+ ).to be(new_value)
84
+ end
63
85
  end
64
86
 
65
- it "should be created if used" do
66
- e = TestClass.create!(name: "Hello", ext_field_1: "Field1")
87
+ shared_examples_for "A model with getters" do |external_field, klass|
88
+ it "is created if used" do
89
+ e = TestClass.new.tap do |x|
90
+ x.name = "Hello"
91
+ x.send("#{external_field}=", "Field1")
92
+ end
93
+ e.save!
67
94
 
68
- expect(AssociationTestClass.count).to eq(1)
69
- expect(e.ext_field_1).to eq "Field1"
95
+ expect(klass.count).to eq(1)
96
+ expect(e.send(external_field)).to eq "Field1"
97
+ end
70
98
  end
71
99
 
72
- context "when underscore flag is true" do
73
- it "should provide underscored methods" do
74
- e = TestClass.create!(_ext_field_2: "_Field2")
100
+ context "when empty saves are enabled" do
101
+ it_behaves_like "A model with getters", :ext_field, AssociationTestClass
102
+ it_behaves_like "A model with setters", :ext_field
75
103
 
104
+ it "is saved when the default associated model is read" do
105
+ e = TestClass.create!(name: "Hello")
106
+ expect(AssociationTestClass.count).to eq(0)
107
+ expect(e.assoc.class).to eq(AssociationTestClass)
108
+ e.save!
76
109
  expect(AssociationTestClass.count).to eq(1)
77
- expect(e._ext_field_2).to eq "_Field2"
110
+ end
111
+
112
+ it "saves explicitly specified empty values" do
113
+ e = TestClass.create!
114
+ e.name = "TEST"
115
+ e.ext_field = nil
116
+ e.save!
117
+ expect(AssociationTestClass.count).to eq(1)
118
+ end
119
+
120
+ it "returns association value with id" do
121
+ e = TestClass.create!
122
+ e.name = "TEST"
123
+ expect(e.assoc).to_not be(nil)
124
+ expect(e.assoc.id).to_not be(nil)
78
125
  end
79
126
  end
80
127
 
81
- it "should provide an accessor that does not build a new object" do
82
- e = TestClass.new(name: "Hello")
128
+ context "when empty saves are disabled" do
129
+ it_behaves_like "A model with getters",
130
+ :ext_field_using_empties_not_saved,
131
+ NoEmptiesAssociationTestClass
132
+ it_behaves_like "A model with setters",
133
+ :ext_field_using_empties_not_saved
134
+
135
+ it "is not saved when the default associated model is read" do
136
+ e = TestClass.create!(name: "Hello")
137
+ expect(e.assoc.class).to eq(AssociationTestClass)
138
+ e.save!
139
+ expect(NoEmptiesAssociationTestClass.count).to eq(0)
140
+ end
83
141
 
84
- e.assoc(use_original: true) # Access without creating
85
- e.save!
86
- expect(AssociationTestClass.count).to eq 0
142
+ it "does not save empty values" do
143
+ e = TestClass.create!
144
+ e.name = "TEST"
145
+ e.ext_field_using_empties_not_saved = nil
146
+ e.save!
147
+ expect(NoEmptiesAssociationTestClass.count).to eq(0)
148
+ end
149
+
150
+ it "does save non-empty values" do
151
+ e = TestClass.create!
152
+ e.name = "TEST"
153
+ e.ext_field_using_empties_not_saved = "Another test"
154
+ e.save!
155
+ expect(NoEmptiesAssociationTestClass.count).to eq(1)
156
+ end
157
+
158
+ it "returns association value without id" do
159
+ e = TestClass.create!
160
+ e.name = "TEST"
161
+ expect(e.no_empties_assoc).to_not be(nil)
162
+ expect(e.no_empties_assoc.id).to be(nil)
163
+ end
87
164
  end
88
165
  end
89
166
  end
data/spec/spec_helper.rb CHANGED
@@ -1,5 +1,14 @@
1
- require "codeclimate-test-reporter"
2
- CodeClimate::TestReporter.start
1
+ # frozen_string_literal: true
2
+
3
+ if ENV["TRAVIS"] == "true" && ENV["CODE_COVERAGE"] == "true"
4
+ require "simplecov"
5
+ require "codecov"
6
+ SimpleCov.formatter = SimpleCov::Formatter::Codecov
7
+ SimpleCov.start do
8
+ # Omit the spec directory from being counted in code coverage calculations.
9
+ add_filter "/spec/"
10
+ end
11
+ end
3
12
 
4
13
  # Connect to an in-memory database for ActiveRecord tests.
5
14
  require "temping"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: external_fields
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sagar Jauhari
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-05-09 00:00:00.000000000 Z
11
+ date: 2021-10-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -25,131 +25,103 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '4.0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: bundler
28
+ name: activesupport
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '1.7'
34
- type: :development
33
+ version: '4.0'
34
+ type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '1.7'
40
+ version: '4.0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: codeclimate-test-reporter
42
+ name: bundler
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: '0.4'
47
+ version: '0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - "~>"
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: '0.4'
54
+ version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
- name: overcommit
56
+ name: codecov
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - "~>"
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: '0.23'
61
+ version: '0'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - "~>"
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
- version: '0.23'
68
+ version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rspec
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - "~>"
73
+ - - ">="
74
74
  - !ruby/object:Gem::Version
75
- version: '3.2'
75
+ version: '0'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - "~>"
80
+ - - ">="
81
81
  - !ruby/object:Gem::Version
82
- version: '3.2'
82
+ version: '0'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: rspec-rails
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - "~>"
88
- - !ruby/object:Gem::Version
89
- version: '3.2'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - "~>"
95
- - !ruby/object:Gem::Version
96
- version: '3.2'
97
- - !ruby/object:Gem::Dependency
98
- name: rubocop
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - "~>"
87
+ - - ">="
102
88
  - !ruby/object:Gem::Version
103
- version: '0.49'
89
+ version: '0'
104
90
  type: :development
105
91
  prerelease: false
106
92
  version_requirements: !ruby/object:Gem::Requirement
107
93
  requirements:
108
- - - "~>"
94
+ - - ">="
109
95
  - !ruby/object:Gem::Version
110
- version: '0.49'
96
+ version: '0'
111
97
  - !ruby/object:Gem::Dependency
112
- name: rubocop-rspec-focused
98
+ name: sqlite3
113
99
  requirement: !ruby/object:Gem::Requirement
114
100
  requirements:
115
- - - "~>"
101
+ - - ">="
116
102
  - !ruby/object:Gem::Version
117
- version: '0.0'
103
+ version: '0'
118
104
  type: :development
119
105
  prerelease: false
120
106
  version_requirements: !ruby/object:Gem::Requirement
121
107
  requirements:
122
- - - "~>"
108
+ - - ">="
123
109
  - !ruby/object:Gem::Version
124
- version: '0.0'
110
+ version: '0'
125
111
  - !ruby/object:Gem::Dependency
126
112
  name: temping
127
113
  requirement: !ruby/object:Gem::Requirement
128
114
  requirements:
129
- - - "~>"
130
- - !ruby/object:Gem::Version
131
- version: '3.2'
132
- type: :development
133
- prerelease: false
134
- version_requirements: !ruby/object:Gem::Requirement
135
- requirements:
136
- - - "~>"
137
- - !ruby/object:Gem::Version
138
- version: '3.2'
139
- - !ruby/object:Gem::Dependency
140
- name: sqlite3
141
- requirement: !ruby/object:Gem::Requirement
142
- requirements:
143
- - - "~>"
115
+ - - ">="
144
116
  - !ruby/object:Gem::Version
145
- version: '1.3'
117
+ version: '0'
146
118
  type: :development
147
119
  prerelease: false
148
120
  version_requirements: !ruby/object:Gem::Requirement
149
121
  requirements:
150
- - - "~>"
122
+ - - ">="
151
123
  - !ruby/object:Gem::Version
152
- version: '1.3'
124
+ version: '0'
153
125
  description: This concern maintains the illusion that a given object has specified
154
126
  attributes, when those attributes are in fact attached to an associated object.
155
127
  This is particularly useful for different classes within a single-table inheritance
@@ -160,10 +132,14 @@ executables: []
160
132
  extensions: []
161
133
  extra_rdoc_files: []
162
134
  files:
135
+ - ".dependabot/config.yml"
136
+ - ".github/workflows/auto-approve-dependabot.yml"
137
+ - ".github/workflows/remove-needs-qa.yml"
138
+ - ".github/workflows/tests.yml"
163
139
  - ".gitignore"
164
- - ".overcommit.yml"
165
140
  - ".rubocop.yml"
166
- - ".travis.yml"
141
+ - CHANGELOG.md
142
+ - CODE_OF_CONDUCT.md
167
143
  - Gemfile
168
144
  - LICENSE
169
145
  - README.md
@@ -176,7 +152,7 @@ homepage: https://github.com/panorama-ed/rails_external_fields
176
152
  licenses:
177
153
  - MIT
178
154
  metadata: {}
179
- post_install_message:
155
+ post_install_message:
180
156
  rdoc_options: []
181
157
  require_paths:
182
158
  - lib
@@ -191,9 +167,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
191
167
  - !ruby/object:Gem::Version
192
168
  version: '0'
193
169
  requirements: []
194
- rubyforge_project:
195
- rubygems_version: 2.7.6
196
- signing_key:
170
+ rubygems_version: 3.0.8
171
+ signing_key:
197
172
  specification_version: 4
198
173
  summary: Access attributes from an associated model.
199
174
  test_files:
data/.overcommit.yml DELETED
@@ -1,60 +0,0 @@
1
- CommitMsg:
2
- CapitalizedSubject:
3
- enabled: true
4
- HardTabs:
5
- enabled: true
6
- RussianNovel:
7
- enabled: true
8
- SingleLineSubject:
9
- enabled: true
10
- TextWidth:
11
- enabled: true
12
- TrailingPeriod:
13
- enabled: true
14
- PreCommit:
15
- AuthorEmail:
16
- enabled: true
17
- AuthorName:
18
- enabled: true
19
- Brakeman: # Performs static code security checking.
20
- enabled: false
21
- BrokenSymlinks:
22
- enabled: true
23
- BundleCheck:
24
- enabled: true
25
- CssLint:
26
- enabled: true
27
- HamlLint:
28
- enabled: true
29
- HardTabs:
30
- enabled: true
31
- HtmlTidy: # Uses the `tidy` executable (installed on OS X by default).
32
- enabled: true
33
- ImageOptim:
34
- enabled: true
35
- Jscs: # Checks for JavaScript style.
36
- enabled: true
37
- JsHint: # Checks for JavaScript best practices.
38
- enabled: true
39
- JsonSyntax:
40
- enabled: true
41
- LocalPathsInGemfile:
42
- enabled: true
43
- MergeConflicts:
44
- enabled: true
45
- PryBinding:
46
- enabled: true
47
- Reek:
48
- enabled: false
49
- RuboCop:
50
- enabled: true
51
- problem_on_unmodified_line: warn
52
- ScssLint:
53
- enabled: true
54
- TrailingWhitespace:
55
- enabled: true
56
- TravisLint: # Checks Travis CI configurations. We use Travis for our open-
57
- # source repositories.
58
- enabled: true
59
- YamlSyntax:
60
- enabled: true
data/.travis.yml DELETED
@@ -1,12 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - 2.2.1
4
- script: bundle exec rspec
5
- addons:
6
- code_climate:
7
- repo_token: 9f025c501f5daf9ffc6712005e4cda18ce4f6afe76c2c0913889712cefb3679e
8
- notifications:
9
- email: false
10
- hipchat:
11
- rooms:
12
- secure: c6XVH78Isrp/TvZsQaq2Ne442rLoUU5FX4N6PgF8jdbMIiPN5dEyURH1LgBop5grv2ZY8BwZeVjNvKVlr8V9wDoER6AV81MclV398qOiIy020dS8ahgpj7F7EO/hKU3iQX4d+3r3a5oeeWjDXdN4uPbeWR5uLXEsXDtX3HUZGxc1p/fT8xUSQufb+kZXccrYV2ryWIa7bQxWlpfAnTm/VQxl3tf5riK7vjq1TkwxvNl4Rv5TjkPFJkryyEcDtutX1jWF8O/nsxzejG604pfyZHcoDjl5erIatalHDwOxr7H1u0XYVpnldiv/L37TGkZaMWuRnCraWn6GDdmqWrpRWYqQfanpdNq9iNB8lWpsPDfnMgLQAbDnSobeeKkkQAeY0lgZjDwcP8AySjEGX/BhdIcYgs6l3C6JR3lj1P47w0KU9YgOxrRf32RGfVRfbT9cz/xqalPjVpJPOmdEoG4vC9tWZTaTBHNnmfJwrcuqnbCLh3mufVvjQD8LDo53xzKJqDguR+j7G0pKfYrwRyRbc1fwBeiVhQwmwtS8u/Swr8ImHN1r50Ma4FId+dBRkstqunfzHjchv/Om96KVPp8rL1sTMHyZvobre8OA4ovjxgQfTFIXholNtOH4Ege/ol9KxF+Sg1fBeELT8c2Jw8Hp46WduyrIcFkRBgNnSqcKnD8=