praxis 0.22.pre.2 → 2.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +323 -324
  3. data/lib/praxis/action_definition.rb +7 -9
  4. data/lib/praxis/api_definition.rb +27 -44
  5. data/lib/praxis/api_general_info.rb +2 -3
  6. data/lib/praxis/application.rb +14 -141
  7. data/lib/praxis/bootloader.rb +1 -2
  8. data/lib/praxis/bootloader_stages/environment.rb +13 -0
  9. data/lib/praxis/controller.rb +0 -2
  10. data/lib/praxis/dispatcher.rb +4 -6
  11. data/lib/praxis/docs/generator.rb +8 -18
  12. data/lib/praxis/docs/link_builder.rb +1 -1
  13. data/lib/praxis/error_handler.rb +5 -5
  14. data/lib/praxis/extensions/attribute_filtering/active_record_filter_query_builder.rb +1 -1
  15. data/lib/praxis/extensions/attribute_filtering/sequel_filter_query_builder.rb +125 -0
  16. data/lib/praxis/extensions/field_selection/active_record_query_selector.rb +16 -18
  17. data/lib/praxis/extensions/field_selection/sequel_query_selector.rb +5 -5
  18. data/lib/praxis/extensions/field_selection.rb +1 -12
  19. data/lib/praxis/extensions/rendering.rb +1 -1
  20. data/lib/praxis/file_group.rb +1 -1
  21. data/lib/praxis/handlers/xml.rb +1 -1
  22. data/lib/praxis/mapper/active_model_compat.rb +63 -0
  23. data/lib/praxis/mapper/resource.rb +242 -0
  24. data/lib/praxis/mapper/selector_generator.rb +126 -0
  25. data/lib/praxis/mapper/sequel_compat.rb +37 -0
  26. data/lib/praxis/middleware_app.rb +13 -15
  27. data/lib/praxis/multipart/part.rb +3 -5
  28. data/lib/praxis/plugins/mapper_plugin.rb +50 -0
  29. data/lib/praxis/request.rb +14 -7
  30. data/lib/praxis/request_stages/response.rb +2 -3
  31. data/lib/praxis/resource_definition.rb +10 -14
  32. data/lib/praxis/response.rb +6 -5
  33. data/lib/praxis/response_definition.rb +5 -7
  34. data/lib/praxis/response_template.rb +3 -4
  35. data/lib/praxis/responses/http.rb +36 -0
  36. data/lib/praxis/responses/internal_server_error.rb +12 -3
  37. data/lib/praxis/responses/multipart_ok.rb +11 -4
  38. data/lib/praxis/responses/validation_error.rb +10 -1
  39. data/lib/praxis/router.rb +3 -3
  40. data/lib/praxis/tasks/api_docs.rb +2 -10
  41. data/lib/praxis/tasks/routes.rb +0 -1
  42. data/lib/praxis/version.rb +1 -1
  43. data/lib/praxis.rb +13 -9
  44. data/praxis.gemspec +2 -3
  45. data/spec/functional_spec.rb +0 -1
  46. data/spec/praxis/action_definition_spec.rb +15 -26
  47. data/spec/praxis/api_definition_spec.rb +8 -13
  48. data/spec/praxis/api_general_info_spec.rb +8 -3
  49. data/spec/praxis/application_spec.rb +7 -13
  50. data/spec/praxis/handlers/xml_spec.rb +2 -2
  51. data/spec/praxis/mapper/resource_spec.rb +169 -0
  52. data/spec/praxis/mapper/selector_generator_spec.rb +301 -0
  53. data/spec/praxis/middleware_app_spec.rb +15 -9
  54. data/spec/praxis/request_spec.rb +7 -17
  55. data/spec/praxis/request_stages/validate_spec.rb +1 -1
  56. data/spec/praxis/resource_definition_spec.rb +10 -12
  57. data/spec/praxis/response_definition_spec.rb +5 -22
  58. data/spec/praxis/response_spec.rb +5 -12
  59. data/spec/praxis/responses/internal_server_error_spec.rb +5 -2
  60. data/spec/praxis/router_spec.rb +4 -8
  61. data/spec/spec_app/app/models/person.rb +3 -3
  62. data/spec/spec_app/config/environment.rb +3 -21
  63. data/spec/spec_app/config.ru +6 -1
  64. data/spec/spec_helper.rb +2 -17
  65. data/spec/support/spec_resources.rb +131 -0
  66. metadata +19 -31
  67. data/lib/praxis/extensions/attribute_filtering/query_builder.rb +0 -39
  68. data/lib/praxis/extensions/attribute_filtering.rb +0 -28
  69. data/lib/praxis/extensions/mapper_selectors.rb +0 -16
  70. data/lib/praxis/media_type_collection.rb +0 -127
  71. data/lib/praxis/plugins/praxis_mapper_plugin.rb +0 -246
  72. data/spec/praxis/media_type_collection_spec.rb +0 -157
  73. data/spec/praxis/plugins/praxis_mapper_plugin_spec.rb +0 -142
@@ -21,35 +21,17 @@ Praxis::Application.configure do |application|
21
21
  application.bootloader.use SimpleAuthenticationPlugin, config_file: 'config/authentication.yml'
22
22
  application.bootloader.use AuthorizationPlugin
23
23
 
24
-
25
- adapter_name = 'sqlite'
26
- db_name = ':memory:'
27
- connection_opts = if RUBY_PLATFORM !~ /java/
28
- { adapter: adapter_name , database: db_name }
29
- else
30
- require 'jdbc/sqlite3'
31
- { adapter: 'jdbc', uri: "jdbc:#{adapter_name}:#{db_name}" }
32
- end
33
-
34
- application.bootloader.use Praxis::Plugins::PraxisMapperPlugin, {
35
- config_data: {
36
- repositories: { default: connection_opts },
37
- log_stats: 'detailed'
38
- }
39
- }
40
-
41
24
  # enable "development-mode" options
42
25
  application.config.praxis.validate_responses = true
43
26
  application.config.praxis.validate_response_bodies = true
44
27
  application.config.praxis.show_exceptions = true
45
28
 
46
- # FIXME: until we have a better way to unit test such a feature...
47
29
  # Silly callback code pieces to test that the deferred callbacks work even for sub-stages
48
- application.bootloader.after :app, :models do
49
- PersonModel.identity(:other_id)
30
+ application.bootloader.after :app, :controllers do
31
+ $after_app_controllers = :worked
50
32
  end
51
33
  application.bootloader.after :app do
52
- raise "After sub-stage hooks not working!" unless PersonModel.identities.include? :other_id
34
+ raise "After sub-stage hooks not working!" unless $after_app_controllers == :worked
53
35
  end
54
36
 
55
37
 
@@ -9,4 +9,9 @@ $:.unshift File.expand_path('lib', __dir__)
9
9
 
10
10
  require 'praxis'
11
11
 
12
- run Praxis::Application.new.setup
12
+
13
+ application = Praxis::Application.instance
14
+
15
+ application.setup
16
+
17
+ run application
data/spec/spec_helper.rb CHANGED
@@ -19,12 +19,10 @@ require 'rack/test'
19
19
  require 'rspec/its'
20
20
  require 'rspec/collection_matchers'
21
21
 
22
-
23
22
  Dir["#{File.dirname(__FILE__)}/../lib/praxis/plugins/*.rb"].each do |file|
24
23
  require file
25
24
  end
26
25
 
27
- APP = Praxis::Application.instance
28
26
  Dir["#{File.dirname(__FILE__)}/support/*.rb"].each do |file|
29
27
  require file
30
28
  end
@@ -33,11 +31,9 @@ RSpec.configure do |config|
33
31
  config.include Rack::Test::Methods
34
32
 
35
33
  config.before(:suite) do
34
+ Praxis::Mapper::Resource.finalize!
36
35
  Praxis::Blueprint.caching_enabled = true
37
- APP.setup(root:'spec/spec_app')
38
-
39
- # create the table
40
- setup_database!
36
+ Praxis::Application.instance.setup(root:'spec/spec_app')
41
37
  end
42
38
 
43
39
  config.before(:each) do
@@ -49,17 +45,6 @@ RSpec.configure do |config|
49
45
  config.before(:all) do
50
46
  # disable logging below warn level
51
47
  Praxis::Application.instance.logger.level = 2 # warn
52
- Thread.current[:praxis_instance] = APP
53
48
  end
54
49
  end
55
50
 
56
- # create the test db schema
57
- def setup_database!
58
- mapper = Praxis::Application.instance.plugins[:praxis_mapper]
59
- Sequel.connect(mapper.config.repositories["default"]['connection_settings'].dump) do |db|
60
- db.create_table! :people do
61
- primary_key :id
62
- string :name
63
- end
64
- end
65
- end
@@ -0,0 +1,131 @@
1
+ require 'ostruct'
2
+
3
+ require 'praxis/mapper/active_model_compat'
4
+ class SimpleModel < OpenStruct
5
+ include Praxis::Mapper::ActiveModelCompat
6
+ def self._praxis_associations
7
+ {
8
+ parent: {
9
+ model: ParentModel,
10
+ primary_key: :id,
11
+ type: :many_to_one
12
+ },
13
+ other_model: {
14
+ model: OtherModel,
15
+ primary_key: :id,
16
+ type: :many_to_one
17
+ }
18
+ }
19
+ end
20
+ end
21
+
22
+ class OtherModel < OpenStruct
23
+ include Praxis::Mapper::ActiveModelCompat
24
+ def self._praxis_associations
25
+ {
26
+ }
27
+ end
28
+ end
29
+
30
+ class PersonModel < OpenStruct
31
+ include Praxis::Mapper::ActiveModelCompat
32
+ def self._praxis_associations
33
+ {
34
+ }
35
+ end
36
+ end
37
+
38
+ class ParentModel < OpenStruct
39
+ include Praxis::Mapper::ActiveModelCompat
40
+ def self._praxis_associations
41
+ {
42
+ }
43
+ end
44
+ end
45
+
46
+ class YamlArrayModel < OpenStruct
47
+ include Praxis::Mapper::ActiveModelCompat
48
+ def self._praxis_associations
49
+ {
50
+ parents: {
51
+ model: ParentModel,
52
+ primary_key: :id,
53
+ type: :one_to_many
54
+ }
55
+ }
56
+ end
57
+ end
58
+
59
+ # A set of resource classes for use in specs
60
+ class BaseResource < Praxis::Mapper::Resource
61
+ def href
62
+ base_href = '' # "/api"
63
+ base_href + "/#{self.class.collection_name}/#{self.id}"
64
+ end
65
+
66
+ property :href, dependencies: [:id]
67
+ end
68
+
69
+ # class CompositeIdResource < BaseResource
70
+ # model CompositeIdModel
71
+ # end
72
+
73
+ class OtherResource < BaseResource
74
+ model OtherModel
75
+ end
76
+
77
+ class ParentResource < BaseResource
78
+ model ParentModel
79
+ end
80
+
81
+ class SimpleResource < BaseResource
82
+ model SimpleModel
83
+
84
+ resource_delegate :other_model => [:other_attribute]
85
+
86
+ def other_resource
87
+ self.other_model
88
+ end
89
+
90
+ property :other_resource, dependencies: [:other_model]
91
+
92
+ property :name, dependencies: [:simple_name]
93
+ end
94
+
95
+ # class SimplerResource < BaseResource
96
+ # model SimplerModel
97
+ # end
98
+
99
+ class YamlArrayResource < BaseResource
100
+ model YamlArrayModel
101
+ end
102
+
103
+ class PersonResource < BaseResource
104
+ model PersonModel
105
+
106
+ def href
107
+ "/people/#{self.id}"
108
+ end
109
+
110
+ end
111
+
112
+ # class AddressResource < BaseResource
113
+ # model AddressModel
114
+
115
+
116
+ # def href
117
+ # "/addresses/#{self.id}"
118
+ # end
119
+ # property :href, dependencies: [:id]
120
+
121
+ # def owner_name
122
+ # self.owner.name
123
+ # end
124
+ # property :owner_name, dependencies: ['owner.name']
125
+
126
+ # def resident_count
127
+ # self.residents.size
128
+ # end
129
+ # property :resident_count, dependencies: [:residents]
130
+
131
+ # end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: praxis
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.22.pre.2
4
+ version: 2.0.pre.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Josep M. Blanquer
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2019-09-17 00:00:00.000000000 Z
12
+ date: 2020-02-22 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rack
@@ -73,20 +73,6 @@ dependencies:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
75
  version: '0'
76
- - !ruby/object:Gem::Dependency
77
- name: praxis-mapper
78
- requirement: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - "~>"
81
- - !ruby/object:Gem::Version
82
- version: '4.3'
83
- type: :runtime
84
- prerelease: false
85
- version_requirements: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - "~>"
88
- - !ruby/object:Gem::Version
89
- version: '4.3'
90
76
  - !ruby/object:Gem::Dependency
91
77
  name: praxis-blueprints
92
78
  requirement: !ruby/object:Gem::Requirement
@@ -119,16 +105,16 @@ dependencies:
119
105
  name: thor
120
106
  requirement: !ruby/object:Gem::Requirement
121
107
  requirements:
122
- - - "~>"
108
+ - - ">="
123
109
  - !ruby/object:Gem::Version
124
- version: '0.18'
110
+ version: '0'
125
111
  type: :runtime
126
112
  prerelease: false
127
113
  version_requirements: !ruby/object:Gem::Requirement
128
114
  requirements:
129
- - - "~>"
115
+ - - ">="
130
116
  - !ruby/object:Gem::Version
131
- version: '0.18'
117
+ version: '0'
132
118
  - !ruby/object:Gem::Dependency
133
119
  name: terminal-table
134
120
  requirement: !ruby/object:Gem::Requirement
@@ -371,16 +357,16 @@ dependencies:
371
357
  name: yard
372
358
  requirement: !ruby/object:Gem::Requirement
373
359
  requirements:
374
- - - "~>"
360
+ - - ">="
375
361
  - !ruby/object:Gem::Version
376
- version: '0'
362
+ version: 0.9.20
377
363
  type: :development
378
364
  prerelease: false
379
365
  version_requirements: !ruby/object:Gem::Requirement
380
366
  requirements:
381
- - - "~>"
367
+ - - ">="
382
368
  - !ruby/object:Gem::Version
383
- version: '0'
369
+ version: 0.9.20
384
370
  - !ruby/object:Gem::Dependency
385
371
  name: coveralls
386
372
  requirement: !ruby/object:Gem::Requirement
@@ -536,16 +522,14 @@ files:
536
522
  - lib/praxis/exceptions/invalid_trait.rb
537
523
  - lib/praxis/exceptions/stage_not_found.rb
538
524
  - lib/praxis/exceptions/validation.rb
539
- - lib/praxis/extensions/attribute_filtering.rb
540
525
  - lib/praxis/extensions/attribute_filtering/active_record_filter_query_builder.rb
541
526
  - lib/praxis/extensions/attribute_filtering/filtering_params.rb
542
- - lib/praxis/extensions/attribute_filtering/query_builder.rb
527
+ - lib/praxis/extensions/attribute_filtering/sequel_filter_query_builder.rb
543
528
  - lib/praxis/extensions/field_expansion.rb
544
529
  - lib/praxis/extensions/field_selection.rb
545
530
  - lib/praxis/extensions/field_selection/active_record_query_selector.rb
546
531
  - lib/praxis/extensions/field_selection/field_selector.rb
547
532
  - lib/praxis/extensions/field_selection/sequel_query_selector.rb
548
- - lib/praxis/extensions/mapper_selectors.rb
549
533
  - lib/praxis/extensions/rails_compat.rb
550
534
  - lib/praxis/extensions/rails_compat/request_methods.rb
551
535
  - lib/praxis/extensions/rendering.rb
@@ -555,8 +539,11 @@ files:
555
539
  - lib/praxis/handlers/www_form.rb
556
540
  - lib/praxis/handlers/xml.rb
557
541
  - lib/praxis/links.rb
542
+ - lib/praxis/mapper/active_model_compat.rb
543
+ - lib/praxis/mapper/resource.rb
544
+ - lib/praxis/mapper/selector_generator.rb
545
+ - lib/praxis/mapper/sequel_compat.rb
558
546
  - lib/praxis/media_type.rb
559
- - lib/praxis/media_type_collection.rb
560
547
  - lib/praxis/media_type_identifier.rb
561
548
  - lib/praxis/middleware_app.rb
562
549
  - lib/praxis/multipart/parser.rb
@@ -564,7 +551,7 @@ files:
564
551
  - lib/praxis/notifications.rb
565
552
  - lib/praxis/plugin.rb
566
553
  - lib/praxis/plugin_concern.rb
567
- - lib/praxis/plugins/praxis_mapper_plugin.rb
554
+ - lib/praxis/plugins/mapper_plugin.rb
568
555
  - lib/praxis/plugins/rails_plugin.rb
569
556
  - lib/praxis/request.rb
570
557
  - lib/praxis/request_stages/action.rb
@@ -629,14 +616,14 @@ files:
629
616
  - spec/praxis/handlers/json_spec.rb
630
617
  - spec/praxis/handlers/xml_spec.rb
631
618
  - spec/praxis/links_spec.rb
632
- - spec/praxis/media_type_collection_spec.rb
619
+ - spec/praxis/mapper/resource_spec.rb
620
+ - spec/praxis/mapper/selector_generator_spec.rb
633
621
  - spec/praxis/media_type_identifier_spec.rb
634
622
  - spec/praxis/media_type_spec.rb
635
623
  - spec/praxis/middleware_app_spec.rb
636
624
  - spec/praxis/multipart/parser_spec.rb
637
625
  - spec/praxis/notifications_spec.rb
638
626
  - spec/praxis/plugin_concern_spec.rb
639
- - spec/praxis/plugins/praxis_mapper_plugin_spec.rb
640
627
  - spec/praxis/request_spec.rb
641
628
  - spec/praxis/request_stages/action_spec.rb
642
629
  - spec/praxis/request_stages/request_stage_spec.rb
@@ -687,6 +674,7 @@ files:
687
674
  - spec/support/spec_complex_authentication_plugin.rb
688
675
  - spec/support/spec_media_types.rb
689
676
  - spec/support/spec_resource_definitions.rb
677
+ - spec/support/spec_resources.rb
690
678
  - spec/support/spec_simple_authentication_plugin.rb
691
679
  - tasks/loader.thor
692
680
  - tasks/thor/app.rb
@@ -1,39 +0,0 @@
1
- # frozen_string_literal: true
2
- require_relative 'active_record_filter_query_builder'
3
-
4
- module Praxis
5
- module Extensions
6
- module QueryBuilder
7
- # To include in a resource object...
8
- extend ActiveSupport::Concern
9
-
10
- included do
11
- # TODO: this shouldn't be needed if we incorporate it with the properties of the mapper...
12
- def self.filters_mapping(hash)
13
- @query_builder_class = ActiveRecordFilterQueryBuilder.for(**hash)
14
- end
15
-
16
- def self.query_builder_class
17
- @query_builder_class
18
- end
19
-
20
- def self.craft_query(base_query, filters) # rubocop:disable Metrics/AbcSize
21
- # Assume QueryBuilder
22
- if query_builder_class
23
- unless query_builder_class.ancestors.include?(ActiveRecordFilterQueryBuilder)
24
- raise ArgumentError, ':query_builder_class must a class extending FilterQueryBuilder'
25
- end
26
-
27
- if filters && query_builder_class
28
- base_query = query_builder_class.new(query: base_query, model: model ).build_clause(filters)
29
- end
30
- # puts "FILTERS_QUERY: #{filters_query.sql}"
31
- end
32
-
33
- base_query
34
- end
35
- end
36
-
37
- end
38
- end
39
- end
@@ -1,28 +0,0 @@
1
- require 'praxis/extensions/attribute_filtering/filtering_params'
2
- require 'praxis/extensions/attribute_filtering/query_builder'
3
-
4
- # To include in a controller
5
- module Praxis
6
- module Extensions
7
- module AttributeFiltering
8
- extend ActiveSupport::Concern
9
-
10
- def build_query(base_query) # rubocop:disable Metrics/AbcSize
11
-
12
- domain_model = self.media_type&.domain_model
13
- raise "No domain model defined for #{self.name}. Cannot use the attribute filtering helpers without it" unless domain_model
14
-
15
- filters = request.params.filters if request.params&.respond_to?(:filters)
16
- base_query = domain_model.craft_query( base_query , filters )
17
-
18
- # TODO: add the field selector...and the pagination...and the ordering...
19
- resolved = Praxis::MediaType::FieldResolver.resolve(self.media_type, self.expanded_fields)
20
- base_query = FieldSelection::ActiveRecordQuerySelector.new(ds: base_query, model: domain_model.model,
21
- selectors: identity_map.selectors, resolved: resolved).generate
22
-
23
- # TODO: handle pagination and ordering
24
- base_query
25
- end
26
- end
27
- end
28
- end
@@ -1,16 +0,0 @@
1
- module Praxis
2
- module Extensions
3
- module MapperSelectors
4
- extend ActiveSupport::Concern
5
- include FieldExpansion
6
-
7
- def set_selectors
8
- return unless self.media_type.respond_to?(:domain_model) &&
9
- self.media_type.domain_model < Praxis::Mapper::Resource
10
-
11
- resolved = Praxis::MediaType::FieldResolver.resolve(self.media_type, self.expanded_fields)
12
- identity_map.add_selectors(self.media_type.domain_model, resolved)
13
- end
14
- end
15
- end
16
- end
@@ -1,127 +0,0 @@
1
- module Praxis
2
-
3
- module StructCollection
4
- def self.included(klass)
5
- klass.instance_eval do
6
- include(Enumerable)
7
- end
8
- end
9
-
10
- def _members=(members)
11
- @members = members
12
- end
13
-
14
- def _members
15
- @members || []
16
- end
17
-
18
- def each
19
- _members.each { |member| yield(member) }
20
- end
21
- end
22
-
23
- class MediaTypeCollection < MediaType
24
- include Enumerable
25
-
26
- class << self
27
- attr_accessor :member_attribute
28
- end
29
-
30
- def self.inherited(klass)
31
- warn "DEPRECATION: MediaTypeCollection is deprecated and will be removed by 1.0"
32
- super
33
- end
34
-
35
- def self._finalize!
36
- super
37
-
38
- if const_defined?(:Struct, false)
39
- self::Struct.instance_eval do
40
- include StructCollection
41
- end
42
- end
43
- end
44
-
45
- def self.member_type(type=nil)
46
- return ( @member_attribute ? @member_attribute.type : nil) unless type
47
- raise ArgumentError, "invalid type: #{type.name}" unless type < MediaType
48
-
49
- member_options = {}
50
- @member_attribute = Attributor::Attribute.new type, member_options
51
- end
52
-
53
- def self.example(context=nil, options: {})
54
- result = super
55
-
56
- context = case context
57
- when nil
58
- ["#{self.name}-#{values.object_id.to_s}"]
59
- when ::String
60
- [context]
61
- else
62
- context
63
- end
64
-
65
- members = []
66
- size = rand(3) + 1
67
-
68
- size.times do |i|
69
- subcontext = context + ["at(#{i})"]
70
- members << @member_attribute.example(subcontext)
71
- end
72
-
73
- result.object._members = members
74
- result
75
- end
76
-
77
- def self.load(value,context=Attributor::DEFAULT_ROOT_CONTEXT, **options)
78
- if value.kind_of?(String)
79
- value = JSON.parse(value)
80
- end
81
-
82
- case value
83
- when nil, self
84
- value
85
- when Hash
86
- # Need to parse/deserialize first
87
- self.new(self.attribute.load(value,context, **options))
88
- when Array, Praxis::Mapper::ResourceDecorator
89
- object = self.attribute.load({})
90
- object._members = value.collect { |subvalue| @member_attribute.load(subvalue) }
91
- self.new(object)
92
- else
93
- # Just wrap whatever value
94
- self.new(value)
95
- end
96
- end
97
-
98
- def self.describe(shallow = false)
99
- hash = super
100
- hash[:member_attribute] = member_attribute.describe(true)
101
- hash
102
- end
103
-
104
- def self.member_view(name, using: nil)
105
- if using
106
- member_view = self.member_type.view(using)
107
- return self.views[name] = CollectionView.new(name, self.member_type, member_view)
108
- end
109
-
110
- self.views[name]
111
- end
112
-
113
-
114
- def each
115
- @object.each { |member| yield(member) }
116
- end
117
-
118
-
119
- def validate(context=Attributor::DEFAULT_ROOT_CONTEXT)
120
- errors = super
121
- self.each_with_object(errors) do |member, errors|
122
- errors.push(*member.validate(context))
123
- end
124
- end
125
-
126
- end
127
- end