rails_view_adapters 0.2.1
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 +7 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/.rubocop.yml +181 -0
- data/.travis.yml +7 -0
- data/Gemfile +4 -0
- data/LICENSE.md +27 -0
- data/README.md +88 -0
- data/Rakefile +6 -0
- data/lib/rails_view_adapters/adapter.rb +34 -0
- data/lib/rails_view_adapters/adapter_base.rb +101 -0
- data/lib/rails_view_adapters/definition_proxy.rb +155 -0
- data/lib/rails_view_adapters/map.rb +46 -0
- data/lib/rails_view_adapters/version.rb +4 -0
- data/lib/rails_view_adapters.rb +13 -0
- data/rails_view_adapters.gemspec +35 -0
- data/spec/active_record_helper.rb +16 -0
- data/spec/active_record_support/db/migrate/20161031222934_create_users.rb +14 -0
- data/spec/active_record_support/db/migrate/20161031223219_create_posts.rb +11 -0
- data/spec/active_record_support/db/migrate/20161031223253_create_teams.rb +10 -0
- data/spec/active_record_support/fabricators/post_fabricator.rb +7 -0
- data/spec/active_record_support/fabricators/team_fabricator.rb +6 -0
- data/spec/active_record_support/fabricators/user_fabricator.rb +10 -0
- data/spec/active_record_support/models/post.rb +4 -0
- data/spec/active_record_support/models/team.rb +4 -0
- data/spec/active_record_support/models/user.rb +5 -0
- data/spec/integration/an_adapter_spec.rb +58 -0
- data/spec/lib/adapter_base_spec.rb +133 -0
- data/spec/lib/adapter_spec.rb +45 -0
- data/spec/lib/definition_proxy_spec.rb +242 -0
- data/spec/lib/map_spec.rb +99 -0
- data/spec/spec_helper.rb +19 -0
- data/spec/support/an_adapter.rb +72 -0
- data/spec/support/fuzzy_nested_match.rb +96 -0
- metadata +206 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 271a96685da34e2fef21042413b046c05bd3b48a
|
4
|
+
data.tar.gz: 9dcb4018a390cec96a55c604435ddbde14ccd023
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 141a9618d9417e8fe45d72863d7c532489d16227ea51ec290657a52f4bdec96d3951356e09af831b0b8fe93ebbf6e3d694545c03c6468565d4d040fb7e61d0db
|
7
|
+
data.tar.gz: b0d4a193b1e85d9d8d563a7dbb999e14a2d8a78b9f82dd503174622ad555c285f2cc6da6d5153ca0eb33943aaecf78b2d286a8f923140c987b6150b913067796
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,181 @@
|
|
1
|
+
# Copyright (c) 2015 The Regents of the University of Michigan.
|
2
|
+
# All Rights Reserved.
|
3
|
+
# Licensed according to the terms of the Revised BSD License
|
4
|
+
# See LICENSE.md for details.
|
5
|
+
|
6
|
+
AllCops:
|
7
|
+
DisplayCopNames: true
|
8
|
+
TargetRubyVersion: 2.3
|
9
|
+
|
10
|
+
|
11
|
+
Style/Alias:
|
12
|
+
EnforcedStyle: prefer_alias_method
|
13
|
+
|
14
|
+
Metrics/LineLength:
|
15
|
+
Max: 100
|
16
|
+
AllowHeredoc: true
|
17
|
+
AllowURI: true
|
18
|
+
URISchemes:
|
19
|
+
- http
|
20
|
+
- https
|
21
|
+
|
22
|
+
Style/FirstParameterIndentation:
|
23
|
+
EnforcedStyle: consistent
|
24
|
+
|
25
|
+
Style/AlignParameters:
|
26
|
+
# Alignment of parameters in multi-line method calls.
|
27
|
+
#
|
28
|
+
# The `with_first_parameter` style aligns the following lines along the same
|
29
|
+
# column as the first parameter.
|
30
|
+
#
|
31
|
+
# method_call(a,
|
32
|
+
# b)
|
33
|
+
#
|
34
|
+
# The `with_fixed_indentation` style aligns the following lines with one
|
35
|
+
# level of indentation relative to the start of the line with the method call.
|
36
|
+
#
|
37
|
+
# method_call(a,
|
38
|
+
# b)
|
39
|
+
EnforcedStyle: with_fixed_indentation
|
40
|
+
|
41
|
+
# Indentation of `when`.
|
42
|
+
Style/CaseIndentation:
|
43
|
+
IndentWhenRelativeTo: end
|
44
|
+
|
45
|
+
# This usually but not always does what we want; disable in individual places
|
46
|
+
# where it gets it wrong
|
47
|
+
Style/ClassAndModuleChildren:
|
48
|
+
EnforcedStyle: nested
|
49
|
+
|
50
|
+
# Checks formatting of special comments
|
51
|
+
Style/CommentAnnotation:
|
52
|
+
Enabled: false
|
53
|
+
|
54
|
+
Style/Copyright:
|
55
|
+
Enabled: false
|
56
|
+
|
57
|
+
Style/EmptyLineBetweenDefs:
|
58
|
+
# If true, this parameter means that single line method definitions don't
|
59
|
+
# need an empty line between them.
|
60
|
+
AllowAdjacentOneLineDefs: true
|
61
|
+
|
62
|
+
Style/EmptyLinesAroundClassBody:
|
63
|
+
Enabled: false
|
64
|
+
|
65
|
+
Style/EmptyLinesAroundModuleBody:
|
66
|
+
Enabled: false
|
67
|
+
|
68
|
+
Style/FileName:
|
69
|
+
# When true, requires that each source file should define a class or module
|
70
|
+
# with a name which matches the file name (converted to ... case).
|
71
|
+
# It further expects it to be nested inside modules which match the names
|
72
|
+
# of subdirectories in its path.
|
73
|
+
ExpectMatchingDefinition: false
|
74
|
+
|
75
|
+
Style/GuardClause:
|
76
|
+
Enabled: false
|
77
|
+
|
78
|
+
Style/IfUnlessModifier:
|
79
|
+
MaxLineLength: 100
|
80
|
+
|
81
|
+
# Checks the indentation of the first element in an array literal.
|
82
|
+
Style/IndentArray:
|
83
|
+
# The value `special_inside_parentheses` means that array literals with
|
84
|
+
# brackets that have their opening bracket on the same line as a surrounding
|
85
|
+
# opening round parenthesis, shall have their first element indented relative
|
86
|
+
# to the first position inside the parenthesis.
|
87
|
+
#
|
88
|
+
# The value `consistent` means that the indentation of the first element shall
|
89
|
+
# always be relative to the first position of the line where the opening
|
90
|
+
# bracket is.
|
91
|
+
#
|
92
|
+
# The value `align_brackets` means that the indentation of the first element
|
93
|
+
# shall always be relative to the position of the opening bracket.
|
94
|
+
EnforcedStyle: consistent
|
95
|
+
|
96
|
+
# Checks the indentation of the first key in a hash literal.
|
97
|
+
Style/IndentHash:
|
98
|
+
# The value `special_inside_parentheses` means that hash literals with braces
|
99
|
+
# that have their opening brace on the same line as a surrounding opening
|
100
|
+
# round parenthesis, shall have their first key indented relative to the
|
101
|
+
# first position inside the parenthesis.
|
102
|
+
#
|
103
|
+
# The value `consistent` means that the indentation of the first key shall
|
104
|
+
# always be relative to the first position of the line where the opening
|
105
|
+
# brace is.
|
106
|
+
#
|
107
|
+
# The value `align_braces` means that the indentation of the first key shall
|
108
|
+
# always be relative to the position of the opening brace.
|
109
|
+
EnforcedStyle: consistent
|
110
|
+
|
111
|
+
|
112
|
+
Style/MultilineMethodCallIndentation:
|
113
|
+
EnforcedStyle: indented
|
114
|
+
|
115
|
+
Style/MultilineOperationIndentation:
|
116
|
+
EnforcedStyle: indented
|
117
|
+
|
118
|
+
Style/Next:
|
119
|
+
Enabled: false
|
120
|
+
|
121
|
+
Style/RedundantReturn:
|
122
|
+
# When true allows code like `return x, y`.
|
123
|
+
AllowMultipleReturnValues: true
|
124
|
+
|
125
|
+
# Use / or %r around regular expressions.
|
126
|
+
Style/RegexpLiteral:
|
127
|
+
# If false, the cop will always recommend using %r if one or more slashes
|
128
|
+
# are found in the regexp string.
|
129
|
+
AllowInnerSlashes: true
|
130
|
+
|
131
|
+
Style/Semicolon:
|
132
|
+
# Allow ; to separate several expressions on the same line.
|
133
|
+
AllowAsExpressionSeparator: true
|
134
|
+
|
135
|
+
Style/StringLiterals:
|
136
|
+
EnforcedStyle: double_quotes
|
137
|
+
|
138
|
+
Style/StringLiteralsInInterpolation:
|
139
|
+
EnforcedStyle: double_quotes
|
140
|
+
|
141
|
+
Style/SpaceInsideBlockBraces:
|
142
|
+
SpaceBeforeBlockParameters: false
|
143
|
+
|
144
|
+
Style/SymbolArray:
|
145
|
+
EnforcedStyle: brackets
|
146
|
+
|
147
|
+
Style/WhileUntilModifier:
|
148
|
+
MaxLineLength: 100
|
149
|
+
|
150
|
+
# checks whether the end keywords are aligned properly for `do` `end` blocks.
|
151
|
+
Lint/BlockAlignment:
|
152
|
+
# The value `start_of_block` means that the `end` should be aligned with line
|
153
|
+
# where the `do` keyword appears.
|
154
|
+
# The value `start_of_line` means it should be aligned with the whole
|
155
|
+
# expression's starting line.
|
156
|
+
# The value `either` means both are allowed.
|
157
|
+
AlignWith: start_of_line
|
158
|
+
|
159
|
+
# Align ends correctly.
|
160
|
+
Lint/EndAlignment:
|
161
|
+
# The value `keyword` means that `end` should be aligned with the matching
|
162
|
+
# keyword (if, while, etc.).
|
163
|
+
# The value `variable` means that in assignments, `end` should be aligned
|
164
|
+
# with the start of the variable on the left hand side of `=`. In all other
|
165
|
+
# situations, `end` should still be aligned with the keyword.
|
166
|
+
# The value `start_of_line` means that `end` should be aligned with the start
|
167
|
+
# of the line which the matching keyword appears on.
|
168
|
+
AlignWith: start_of_line
|
169
|
+
|
170
|
+
Lint/DefEndAlignment:
|
171
|
+
# The value `def` means that `end` should be aligned with the def keyword.
|
172
|
+
# The value `start_of_line` means that `end` should be aligned with method
|
173
|
+
# calls like `private`, `public`, etc, if present in front of the `def`
|
174
|
+
# keyword on the same line.
|
175
|
+
AlignWith: def
|
176
|
+
|
177
|
+
Performance/RedundantMerge:
|
178
|
+
Enabled: false
|
179
|
+
|
180
|
+
Style/WordArray:
|
181
|
+
EnforcedStyle: brackets
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.md
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
Copyright (c) 2015, The Regents of the University of Michigan.
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
5
|
+
modification, are permitted provided that the following conditions are
|
6
|
+
met:
|
7
|
+
|
8
|
+
* Redistributions of source code must retain the above copyright
|
9
|
+
notice, this list of conditions and the following disclaimer.
|
10
|
+
* Redistributions in binary form must reproduce the above copyright
|
11
|
+
notice, this list of conditions and the following disclaimer in the
|
12
|
+
documentation and/or other materials provided with the distribution.
|
13
|
+
* Neither the name of the The University of Michigan nor the
|
14
|
+
names of its contributors may be used to endorse or promote products
|
15
|
+
derived from this software without specific prior written permission.
|
16
|
+
|
17
|
+
THIS SOFTWARE IS PROVIDED BY THE REGENTS OF THE UNIVERSITY OF MICHIGAN AND
|
18
|
+
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
|
19
|
+
NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
20
|
+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OF THE
|
21
|
+
UNIVERSITY OF MICHIGAN BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
22
|
+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
23
|
+
TO,PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
24
|
+
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
25
|
+
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
26
|
+
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
27
|
+
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
# Rails View Adapters
|
2
|
+
|
3
|
+
This gem provides a
|
4
|
+
[DSL](http://www.rubydoc.info/github/mlibrary/rails_view_adapters/master/RailsViewAdapters/DefinitionProxy)
|
5
|
+
for defining adapters that map your model's
|
6
|
+
representations to those of your views, and vice versa.
|
7
|
+
|
8
|
+
### Adapters are presenters
|
9
|
+
|
10
|
+
The adapters can be used to convert a model to its public representation,
|
11
|
+
including associated models, as well as support for arbitrary operations.
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
FooAdapter.from_model(Foo.find(id)).to_public_hash
|
15
|
+
```
|
16
|
+
|
17
|
+
### Adapters wrap input parameters
|
18
|
+
|
19
|
+
The adapters also understand how to "undo" the presentation logic,
|
20
|
+
converting the public representation back to the model or models
|
21
|
+
that (may have) generated it.
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
Bar.new(BarAdapter.from_public(params).to_params_hash)
|
25
|
+
```
|
26
|
+
|
27
|
+
## Usage
|
28
|
+
|
29
|
+
### Basics
|
30
|
+
|
31
|
+
Define your adapter using the DSL.
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
# lib/adapters/team_member_adapter.rb
|
35
|
+
require "rails_view_adapters"
|
36
|
+
RailsViewAdapters::Adapter.define(:team_member_adapter) do
|
37
|
+
map_simple :name, :author
|
38
|
+
map_date :join_date, :member_since, date_format
|
39
|
+
map_date :created_at, :created_at, date_format
|
40
|
+
map_date :updated_at, :updated_at, date_format
|
41
|
+
map_bool :admin, :super_user
|
42
|
+
hidden_field :secret
|
43
|
+
map_from_public :secret do |token|
|
44
|
+
{ secret: token }
|
45
|
+
end
|
46
|
+
map_belongs_to :team, :favorite_team, model_class: Team
|
47
|
+
map_has_many :posts, :all_posts, sub_method: :body
|
48
|
+
end
|
49
|
+
```
|
50
|
+
|
51
|
+
Then require and use it like you would any other class.
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
require "lib/adapters/team_member_adapter"
|
55
|
+
TeamMemberAdapter.from_model(TeamMember.find(params[:id])).to_public_hash
|
56
|
+
```
|
57
|
+
|
58
|
+
### Rails
|
59
|
+
|
60
|
+
I've found it convenient to create a concern that gets invoked in a controller
|
61
|
+
`before_action` to automatically grab the right adapter, instantiate it, and use
|
62
|
+
it to modify the params hash. Something like this:
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
module Adaptation
|
66
|
+
extend ActiveSupport::Concern
|
67
|
+
|
68
|
+
included do
|
69
|
+
append_before_action :adapt_params
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
def adapt_params
|
74
|
+
adapter = "#{controller_path.classify.gsub("Controller", "")}Adapter".constantize
|
75
|
+
params.merge!(adapter.from_public(params).to_params_hash) {|key,lhs,rhs| rhs}
|
76
|
+
end
|
77
|
+
end
|
78
|
+
```
|
79
|
+
|
80
|
+
### Testing Adapters
|
81
|
+
|
82
|
+
Individual needs will vary, but for a reasonable integration test take a look at
|
83
|
+
`spec/integration/an_adapter_spec.rb`
|
84
|
+
|
85
|
+
Do note that the above relies on your controllers and adapters being named predictably.
|
86
|
+
|
87
|
+
## Documentation
|
88
|
+
http://www.rubydoc.info/github/mlibrary/rails_view_adapters/
|
data/Rakefile
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "rails_view_adapters/definition_proxy"
|
3
|
+
require "rails_view_adapters/map"
|
4
|
+
require "rails_view_adapters/adapter_base"
|
5
|
+
|
6
|
+
module RailsViewAdapters
|
7
|
+
|
8
|
+
# Top level namespace for defining the adapters.
|
9
|
+
module Adapter
|
10
|
+
|
11
|
+
FIELDS = [
|
12
|
+
:model_fields, :public_fields,
|
13
|
+
:to_maps, :from_maps, :simple_maps
|
14
|
+
].freeze
|
15
|
+
|
16
|
+
def self.define(name, &block)
|
17
|
+
proxy = DefinitionProxy.new(Map.new)
|
18
|
+
proxy.instance_eval(&block)
|
19
|
+
Object.const_set(name.to_s.classify, adapter_from_map(proxy.map))
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.adapter_from_map(map)
|
23
|
+
Class.new(AdapterBase) do
|
24
|
+
FIELDS.each do |method|
|
25
|
+
define_singleton_method method do
|
26
|
+
map.send(method)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# Copyright (c) 2015 The Regents of the University of Michigan.
|
3
|
+
# All Rights Reserved.
|
4
|
+
# Licensed according to the terms of the Revised BSD License
|
5
|
+
# See LICENSE.md for details.
|
6
|
+
|
7
|
+
require "active_support/core_ext/hash"
|
8
|
+
|
9
|
+
module RailsViewAdapters
|
10
|
+
|
11
|
+
# Base class on which adapters are defined.
|
12
|
+
class AdapterBase
|
13
|
+
# Create an instance from an ActiveRecord model.
|
14
|
+
# @param [ActiveRecord::AdapterBase] model
|
15
|
+
# @return the adapter
|
16
|
+
def self.from_model(model)
|
17
|
+
internals = {}
|
18
|
+
model_fields.each do |field|
|
19
|
+
internals[field] = model.send(field)
|
20
|
+
end
|
21
|
+
new(internals, {})
|
22
|
+
end
|
23
|
+
|
24
|
+
# Create an instance from a public representation.
|
25
|
+
# @param [ActionController::Parameters] public
|
26
|
+
# @return the adapter
|
27
|
+
def self.from_public(public)
|
28
|
+
internals = {}
|
29
|
+
simple_maps.each do |model_field, public_field|
|
30
|
+
internals[model_field] = public[public_field]
|
31
|
+
end
|
32
|
+
|
33
|
+
extras = {}
|
34
|
+
(public.keys.map(&:to_sym) - public_fields).each do |extra_key|
|
35
|
+
extras[extra_key] = public[extra_key]
|
36
|
+
end
|
37
|
+
|
38
|
+
from_maps.each do |public_field, process|
|
39
|
+
internals.merge!(process.call(public[public_field]))
|
40
|
+
end
|
41
|
+
|
42
|
+
new(internals, extras)
|
43
|
+
end
|
44
|
+
|
45
|
+
def initialize(internals, extras)
|
46
|
+
@internals = internals
|
47
|
+
@extras = extras
|
48
|
+
@public_hash = nil
|
49
|
+
@params_hash = nil
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_params_hash
|
53
|
+
@params_hash ||= to_model_hash.merge(@extras.symbolize_keys) {|_key, lhs, _rhs| lhs }
|
54
|
+
end
|
55
|
+
|
56
|
+
def to_json(options = {})
|
57
|
+
to_public_hash.to_json(options)
|
58
|
+
end
|
59
|
+
|
60
|
+
def to_model_hash
|
61
|
+
@internals
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_public_hash
|
65
|
+
unless @public_hash
|
66
|
+
@public_hash = {}
|
67
|
+
simple_maps.each do |model_field, public_field|
|
68
|
+
@public_hash[public_field] = @internals[model_field]
|
69
|
+
end
|
70
|
+
to_maps.each do |model_field, process|
|
71
|
+
@public_hash.merge!(process.call(@internals[model_field])) do |k, l, r|
|
72
|
+
merge_strategy.call(k, l, r)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
@public_hash
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
# Define an instance method for each of the class methods
|
82
|
+
# we want to access, otherwise we have to prepend
|
83
|
+
# "self.class." each time.
|
84
|
+
[:simple_maps, :to_maps].each do |method_name|
|
85
|
+
define_method(method_name) do
|
86
|
+
self.class.send(method_name)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def merge_strategy
|
91
|
+
@merge_strategy ||= proc do |_key, lhs, rhs|
|
92
|
+
if lhs.respond_to?(:merge) && rhs.respond_to?(:merge)
|
93
|
+
lhs.merge(rhs)
|
94
|
+
else
|
95
|
+
rhs
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module RailsViewAdapters
|
3
|
+
|
4
|
+
# Defines the DSL methods that are used to modify the underlying
|
5
|
+
# map. This class is only used to evaluate the DSL calls, thereby
|
6
|
+
# modifying the Map.
|
7
|
+
class DefinitionProxy
|
8
|
+
attr_accessor :map
|
9
|
+
def initialize(adapter_map)
|
10
|
+
@map = adapter_map
|
11
|
+
end
|
12
|
+
|
13
|
+
# Register a simple one-to-one mapping.
|
14
|
+
# @param [Symbol] model_field
|
15
|
+
# @param [Symbol] public_field
|
16
|
+
def map_simple(model_field, public_field)
|
17
|
+
map.add_simple_map(model_field, public_field)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Register a mapping from a model field to the public representation.
|
21
|
+
# @param [Symbol] model_field
|
22
|
+
# @param [Array<Symbol>] extra_public_fields Used to tell the adapter about
|
23
|
+
# extra public fields created by this mapping.
|
24
|
+
# @yield [model_value] Given the value of the model's model_field, return
|
25
|
+
# a hash of key:values pairs to merge into the public representation.
|
26
|
+
def map_to_public(model_field, extra_public_fields = [], &block)
|
27
|
+
map.add_to_map(model_field, &block)
|
28
|
+
extra_public_fields.each do |public_field|
|
29
|
+
map.add_public_field(public_field)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Register a mapping from a public field to the model representation.
|
34
|
+
# @param [Symbol] public_field
|
35
|
+
# @yield [public_value] Given the value of public representation's
|
36
|
+
# public_field, return a hash of key:value pairs to merge into
|
37
|
+
# the internal model representation.
|
38
|
+
def map_from_public(public_field, &block)
|
39
|
+
map.add_from_map(public_field, &block)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Register a hidden field, i.e. a field not present in public representations.
|
43
|
+
# @param [Symbol] model_field
|
44
|
+
def hidden_field(model_field)
|
45
|
+
map.add_model_field(model_field)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Register a one-to-one mapping of a date field.
|
49
|
+
# @param [Symbol] model_field
|
50
|
+
# @param [Symbol] public_field
|
51
|
+
# @param [String] date_format The Date format to use.
|
52
|
+
def map_date(model_field, public_field, date_format)
|
53
|
+
raise ArgumentError if date_format.nil?
|
54
|
+
map_from_public public_field do |value|
|
55
|
+
{ model_field => time_from_public(value) }
|
56
|
+
end
|
57
|
+
map_to_public model_field do |value|
|
58
|
+
{ public_field => value.utc.strftime(date_format) }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Register a one-to-one mapping of a boolean field
|
63
|
+
# @param [Symbol] model_field
|
64
|
+
# @param [Symbol] public_field
|
65
|
+
def map_bool(model_field, public_field)
|
66
|
+
map_from_public public_field do |value|
|
67
|
+
{ model_field => to_bool(value) }
|
68
|
+
end
|
69
|
+
|
70
|
+
map_to_public model_field do |value|
|
71
|
+
{ public_field => value }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Register a mapping of a belongs_to association.
|
76
|
+
# @param [Symbol] model_field The field on the model that holds the association,
|
77
|
+
# usually the association's name.
|
78
|
+
# @param [Symbol] public_field The public field.
|
79
|
+
# @param [Hash] options
|
80
|
+
# @option options [Class] :model_class The class of the associated model,
|
81
|
+
# if it cannot be inferred from the model_field.
|
82
|
+
# @option options [Symbol] :sub_method The method of the association model
|
83
|
+
# that holds the desired data. Default is :id.
|
84
|
+
# @option options [Symbol] :only Only create the to_map or the from_map, as
|
85
|
+
# directed by setting this to :to or :from, respectively.
|
86
|
+
def map_belongs_to(model_field, public_field, options = {})
|
87
|
+
model_class = options[:model_class] || model_field.to_s.classify.constantize
|
88
|
+
sub_method = options[:sub_method] || :id
|
89
|
+
model_field_id = :"#{model_field.to_s.sub(/(_id|)\Z/, "_id")}"
|
90
|
+
|
91
|
+
unless options[:only] == :to
|
92
|
+
map_from_public public_field do |value|
|
93
|
+
record = model_class.send(:"find_by_#{sub_method}", value)
|
94
|
+
{ model_field_id => record ? record.id : nil }
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
unless options[:only] == :from
|
99
|
+
map_to_public model_field_id do |id|
|
100
|
+
{ public_field => model_class.find_by(id: id).send(sub_method) }
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Register a mapping of a has_many association.
|
106
|
+
# @param [Symbol] model_field The field on the model that holds the association,
|
107
|
+
# usually the association's name.
|
108
|
+
# @param [Symbol] public_field The public field.
|
109
|
+
# @param [Hash] options
|
110
|
+
# @option options [Class] :model_class The class of the model, if it cannot
|
111
|
+
# be inferred from the model_field.
|
112
|
+
# @option options [Symbol] :sub_method The method of the association model
|
113
|
+
# that holds the desired data. If this isn't provided, it's assumed
|
114
|
+
# to be the same as public_field.
|
115
|
+
def map_has_many(model_field, public_field, options = {})
|
116
|
+
model_class = options[:model_class] || model_field.to_s.classify.constantize
|
117
|
+
sub_method = options[:sub_method] || public_field
|
118
|
+
|
119
|
+
unless options[:only] == :to
|
120
|
+
map_from_public public_field do |value|
|
121
|
+
result = { model_field => model_class.where(sub_method => value) }
|
122
|
+
public_field_size = value.respond_to?(:size) ? value.size : 0
|
123
|
+
result[model_field] = result[model_field]
|
124
|
+
.to_a
|
125
|
+
.fill(nil, result[model_field].size, public_field_size - result[model_field].size)
|
126
|
+
result
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
unless options[:only] == :from
|
131
|
+
map_to_public model_field do |records|
|
132
|
+
{ public_field => records.map(&sub_method.to_sym) }
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
private
|
138
|
+
|
139
|
+
def time_from_public(time)
|
140
|
+
if time.is_a? String
|
141
|
+
Time.zone.parse(time)
|
142
|
+
else
|
143
|
+
time
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def to_bool(value)
|
148
|
+
return nil if value.nil? || value =~ /^(null|nil)$/i
|
149
|
+
return true if value == true || value =~ /^(true|t|yes|y|1)$/i
|
150
|
+
return false if value == false || value =~ /^(false|f|no|n|0)$/i
|
151
|
+
raise ArgumentError, "invalid value for boolean: \"#{value}\""
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module RailsViewAdapters
|
3
|
+
|
4
|
+
# Contains the information needed by the adapters
|
5
|
+
# to convert from one form to another.
|
6
|
+
class Map
|
7
|
+
attr_accessor :model_fields, :public_fields
|
8
|
+
attr_accessor :simple_maps, :to_maps, :from_maps
|
9
|
+
def initialize
|
10
|
+
@simple_maps = []
|
11
|
+
@model_fields = []
|
12
|
+
@public_fields = []
|
13
|
+
@to_maps = []
|
14
|
+
@from_maps = []
|
15
|
+
end
|
16
|
+
|
17
|
+
def add_model_field(model_field)
|
18
|
+
model_fields << model_field
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_public_field(public_field)
|
23
|
+
public_fields << public_field
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def add_simple_map(model_field, public_field)
|
28
|
+
simple_maps << [model_field, public_field]
|
29
|
+
add_model_field(model_field)
|
30
|
+
add_public_field(public_field)
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
def add_to_map(model_field, &block)
|
35
|
+
to_maps << [model_field, block]
|
36
|
+
add_model_field(model_field)
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
def add_from_map(public_field, &block)
|
41
|
+
from_maps << [public_field, block]
|
42
|
+
add_public_field(public_field)
|
43
|
+
self
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|