external_fields 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
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=