rails_view_adapters 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +181 -0
  5. data/.travis.yml +7 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE.md +27 -0
  8. data/README.md +88 -0
  9. data/Rakefile +6 -0
  10. data/lib/rails_view_adapters/adapter.rb +34 -0
  11. data/lib/rails_view_adapters/adapter_base.rb +101 -0
  12. data/lib/rails_view_adapters/definition_proxy.rb +155 -0
  13. data/lib/rails_view_adapters/map.rb +46 -0
  14. data/lib/rails_view_adapters/version.rb +4 -0
  15. data/lib/rails_view_adapters.rb +13 -0
  16. data/rails_view_adapters.gemspec +35 -0
  17. data/spec/active_record_helper.rb +16 -0
  18. data/spec/active_record_support/db/migrate/20161031222934_create_users.rb +14 -0
  19. data/spec/active_record_support/db/migrate/20161031223219_create_posts.rb +11 -0
  20. data/spec/active_record_support/db/migrate/20161031223253_create_teams.rb +10 -0
  21. data/spec/active_record_support/fabricators/post_fabricator.rb +7 -0
  22. data/spec/active_record_support/fabricators/team_fabricator.rb +6 -0
  23. data/spec/active_record_support/fabricators/user_fabricator.rb +10 -0
  24. data/spec/active_record_support/models/post.rb +4 -0
  25. data/spec/active_record_support/models/team.rb +4 -0
  26. data/spec/active_record_support/models/user.rb +5 -0
  27. data/spec/integration/an_adapter_spec.rb +58 -0
  28. data/spec/lib/adapter_base_spec.rb +133 -0
  29. data/spec/lib/adapter_spec.rb +45 -0
  30. data/spec/lib/definition_proxy_spec.rb +242 -0
  31. data/spec/lib/map_spec.rb +99 -0
  32. data/spec/spec_helper.rb +19 -0
  33. data/spec/support/an_adapter.rb +72 -0
  34. data/spec/support/fuzzy_nested_match.rb +96 -0
  35. 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
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ /.idea/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
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
@@ -0,0 +1,7 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.1
5
+ - 2.2.1
6
+
7
+ before_install: gem install bundler -v 1.13.6
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rails_view_adapters.gemspec
4
+ gemspec
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,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -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
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ module RailsViewAdapters
3
+ VERSION = "0.2.1"
4
+ end