brainstem 1.0.0.pre.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/Gemfile.lock +1 -1
  4. data/README.md +383 -32
  5. data/bin/brainstem +6 -0
  6. data/brainstem.gemspec +2 -0
  7. data/docs/api_doc_generator.markdown +175 -0
  8. data/docs/brainstem_executable.markdown +32 -0
  9. data/docs/docgen.png +0 -0
  10. data/docs/docgen_ascii.txt +63 -0
  11. data/docs/executable.png +0 -0
  12. data/docs/executable_ascii.txt +10 -0
  13. data/lib/brainstem/api_docs.rb +146 -0
  14. data/lib/brainstem/api_docs/abstract_collection.rb +116 -0
  15. data/lib/brainstem/api_docs/atlas.rb +158 -0
  16. data/lib/brainstem/api_docs/builder.rb +167 -0
  17. data/lib/brainstem/api_docs/controller.rb +122 -0
  18. data/lib/brainstem/api_docs/controller_collection.rb +40 -0
  19. data/lib/brainstem/api_docs/endpoint.rb +234 -0
  20. data/lib/brainstem/api_docs/endpoint_collection.rb +58 -0
  21. data/lib/brainstem/api_docs/exceptions.rb +8 -0
  22. data/lib/brainstem/api_docs/formatters/abstract_formatter.rb +64 -0
  23. data/lib/brainstem/api_docs/formatters/markdown/controller_formatter.rb +76 -0
  24. data/lib/brainstem/api_docs/formatters/markdown/endpoint_collection_formatter.rb +73 -0
  25. data/lib/brainstem/api_docs/formatters/markdown/endpoint_formatter.rb +169 -0
  26. data/lib/brainstem/api_docs/formatters/markdown/helper.rb +76 -0
  27. data/lib/brainstem/api_docs/formatters/markdown/presenter_formatter.rb +200 -0
  28. data/lib/brainstem/api_docs/introspectors/abstract_introspector.rb +100 -0
  29. data/lib/brainstem/api_docs/introspectors/rails_introspector.rb +232 -0
  30. data/lib/brainstem/api_docs/presenter.rb +225 -0
  31. data/lib/brainstem/api_docs/presenter_collection.rb +97 -0
  32. data/lib/brainstem/api_docs/resolver.rb +73 -0
  33. data/lib/brainstem/api_docs/sinks/abstract_sink.rb +37 -0
  34. data/lib/brainstem/api_docs/sinks/controller_presenter_multifile_sink.rb +93 -0
  35. data/lib/brainstem/api_docs/sinks/stdout_sink.rb +44 -0
  36. data/lib/brainstem/cli.rb +146 -0
  37. data/lib/brainstem/cli/abstract_command.rb +97 -0
  38. data/lib/brainstem/cli/generate_api_docs_command.rb +169 -0
  39. data/lib/brainstem/concerns/controller_dsl.rb +300 -0
  40. data/lib/brainstem/concerns/controller_param_management.rb +30 -9
  41. data/lib/brainstem/concerns/formattable.rb +38 -0
  42. data/lib/brainstem/concerns/inheritable_configuration.rb +3 -2
  43. data/lib/brainstem/concerns/optional.rb +43 -0
  44. data/lib/brainstem/concerns/presenter_dsl.rb +76 -15
  45. data/lib/brainstem/controller_methods.rb +6 -3
  46. data/lib/brainstem/dsl/association.rb +6 -3
  47. data/lib/brainstem/dsl/associations_block.rb +6 -3
  48. data/lib/brainstem/dsl/base_block.rb +2 -4
  49. data/lib/brainstem/dsl/conditional.rb +7 -3
  50. data/lib/brainstem/dsl/conditionals_block.rb +4 -4
  51. data/lib/brainstem/dsl/configuration.rb +184 -8
  52. data/lib/brainstem/dsl/field.rb +6 -3
  53. data/lib/brainstem/dsl/fields_block.rb +2 -3
  54. data/lib/brainstem/help_text.txt +8 -0
  55. data/lib/brainstem/presenter.rb +27 -6
  56. data/lib/brainstem/presenter_validator.rb +5 -2
  57. data/lib/brainstem/time_classes.rb +1 -1
  58. data/lib/brainstem/version.rb +1 -1
  59. data/spec/brainstem/api_docs/abstract_collection_spec.rb +156 -0
  60. data/spec/brainstem/api_docs/atlas_spec.rb +353 -0
  61. data/spec/brainstem/api_docs/builder_spec.rb +100 -0
  62. data/spec/brainstem/api_docs/controller_collection_spec.rb +92 -0
  63. data/spec/brainstem/api_docs/controller_spec.rb +225 -0
  64. data/spec/brainstem/api_docs/endpoint_collection_spec.rb +144 -0
  65. data/spec/brainstem/api_docs/endpoint_spec.rb +346 -0
  66. data/spec/brainstem/api_docs/formatters/abstract_formatter_spec.rb +30 -0
  67. data/spec/brainstem/api_docs/formatters/markdown/controller_formatter_spec.rb +126 -0
  68. data/spec/brainstem/api_docs/formatters/markdown/endpoint_collection_formatter_spec.rb +85 -0
  69. data/spec/brainstem/api_docs/formatters/markdown/endpoint_formatter_spec.rb +261 -0
  70. data/spec/brainstem/api_docs/formatters/markdown/helper_spec.rb +100 -0
  71. data/spec/brainstem/api_docs/formatters/markdown/presenter_formatter_spec.rb +485 -0
  72. data/spec/brainstem/api_docs/introspectors/abstract_introspector_spec.rb +192 -0
  73. data/spec/brainstem/api_docs/introspectors/rails_introspector_spec.rb +170 -0
  74. data/spec/brainstem/api_docs/presenter_collection_spec.rb +84 -0
  75. data/spec/brainstem/api_docs/presenter_spec.rb +519 -0
  76. data/spec/brainstem/api_docs/resolver_spec.rb +72 -0
  77. data/spec/brainstem/api_docs/sinks/abstract_sink_spec.rb +16 -0
  78. data/spec/brainstem/api_docs/sinks/controller_presenter_multifile_sink_spec.rb +56 -0
  79. data/spec/brainstem/api_docs/sinks/stdout_sink_spec.rb +22 -0
  80. data/spec/brainstem/api_docs_spec.rb +58 -0
  81. data/spec/brainstem/cli/abstract_command_spec.rb +91 -0
  82. data/spec/brainstem/cli/generate_api_docs_command_spec.rb +125 -0
  83. data/spec/brainstem/cli_spec.rb +67 -0
  84. data/spec/brainstem/concerns/controller_dsl_spec.rb +471 -0
  85. data/spec/brainstem/concerns/controller_param_management_spec.rb +36 -16
  86. data/spec/brainstem/concerns/formattable_spec.rb +30 -0
  87. data/spec/brainstem/concerns/inheritable_configuration_spec.rb +104 -4
  88. data/spec/brainstem/concerns/optional_spec.rb +48 -0
  89. data/spec/brainstem/concerns/presenter_dsl_spec.rb +202 -31
  90. data/spec/brainstem/dsl/association_spec.rb +18 -2
  91. data/spec/brainstem/dsl/conditional_spec.rb +25 -2
  92. data/spec/brainstem/dsl/configuration_spec.rb +1 -1
  93. data/spec/brainstem/dsl/field_spec.rb +18 -2
  94. data/spec/brainstem/presenter_collection_spec.rb +10 -2
  95. data/spec/brainstem/presenter_spec.rb +32 -0
  96. data/spec/brainstem/presenter_validator_spec.rb +12 -7
  97. data/spec/dummy/rails.rb +49 -0
  98. data/spec/shared/atlas_taker.rb +18 -0
  99. data/spec/shared/formattable.rb +14 -0
  100. data/spec/spec_helper.rb +2 -0
  101. data/spec/spec_helpers/db.rb +1 -1
  102. data/spec/spec_helpers/presenters.rb +20 -14
  103. metadata +106 -6
@@ -2,41 +2,61 @@ require 'spec_helper'
2
2
  require 'brainstem/concerns/controller_param_management'
3
3
 
4
4
  describe Brainstem::Concerns::ControllerParamManagement do
5
- class TasksController
6
- include Brainstem::Concerns::ControllerParamManagement
5
+ subject do
6
+ Class.new do
7
+ include Brainstem::Concerns::ControllerParamManagement
7
8
 
8
- def controller_name
9
- 'tasks'
9
+ def controller_name
10
+ self.class.controller_name
11
+ end
12
+
13
+ def self.controller_name
14
+ 'tasks'
15
+ end
10
16
  end
11
17
  end
12
18
 
13
19
  before do
14
- TasksController.brainstem_model_name = nil
15
- TasksController.brainstem_plural_model_name = nil
20
+ subject.brainstem_model_name = nil
21
+ subject.brainstem_plural_model_name = nil
16
22
  end
17
23
 
18
24
  describe '.brainstem_model_name' do
19
25
  it 'is settable on the controller' do
20
- TasksController.brainstem_model_name = 'thingy'
21
- expect(TasksController.new.brainstem_model_name).to eq 'thingy'
26
+ subject.brainstem_model_name = 'thingy'
27
+ expect(subject.new.brainstem_model_name).to eq 'thingy'
22
28
  end
23
29
 
24
30
  it 'has good defaults' do
25
- expect(TasksController.new.brainstem_model_name).to eq 'task'
26
- expect(TasksController.new.brainstem_plural_model_name).to eq 'tasks'
31
+ expect(subject.new.brainstem_model_name).to eq 'task'
32
+ expect(subject.new.brainstem_plural_model_name).to eq 'tasks'
33
+ end
34
+
35
+ it "has good defaults on the class level" do
36
+ expect(subject.brainstem_model_name).to eq 'task'
37
+ expect(subject.brainstem_plural_model_name).to eq 'tasks'
27
38
  end
28
39
  end
29
40
 
30
41
  describe '.brainstem_plural_model_name' do
31
42
  it 'is infered from the singular model name' do
32
- TasksController.brainstem_model_name = 'thingy'
33
- expect(TasksController.new.brainstem_plural_model_name).to eq 'thingies'
43
+ subject.brainstem_model_name = 'thingy'
44
+ expect(subject.brainstem_plural_model_name).to eq 'thingies'
45
+ expect(subject.new.brainstem_plural_model_name).to eq 'thingies'
34
46
  end
35
47
 
36
48
  it 'can be overridden' do
37
- TasksController.brainstem_model_name = 'thingy'
38
- TasksController.brainstem_plural_model_name = 'thingzees'
39
- expect(TasksController.new.brainstem_plural_model_name).to eq 'thingzees'
49
+ subject.brainstem_model_name = 'thingy'
50
+ subject.brainstem_plural_model_name = 'thingzees'
51
+ expect(subject.brainstem_plural_model_name).to eq 'thingzees'
52
+ expect(subject.new.brainstem_plural_model_name).to eq 'thingzees'
53
+ end
54
+ end
55
+
56
+ describe '.brainstem_model_class' do
57
+ it "classifies and constantizes the brainstem_model_name" do
58
+ subject.brainstem_model_name = "object"
59
+ expect(subject.brainstem_model_class).to eq Object
40
60
  end
41
61
  end
42
- end
62
+ end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+ require 'brainstem/concerns/formattable'
3
+
4
+ module Brainstem
5
+ module Concerns
6
+ describe Formattable do
7
+ let(:formattable_class) do
8
+ Class.new do
9
+ include Brainstem::Concerns::Formattable
10
+ end
11
+ end
12
+
13
+ let(:subject) { formattable_class.new }
14
+
15
+ describe "#formatter_type" do
16
+ it "returns the class name underscored and symbolized" do
17
+ stub(formattable_class).to_s { "MyClass" }
18
+ expect(subject.formatter_type).to eq :my_class
19
+ end
20
+
21
+ it "returns only the last segment of a class name" do
22
+ stub(formattable_class).to_s { "Namespaced::MyClass" }
23
+ expect(subject.formatter_type).to eq :my_class
24
+
25
+ end
26
+ end
27
+
28
+ end
29
+ end
30
+ end
@@ -33,22 +33,51 @@ describe Brainstem::Concerns::InheritableConfiguration do
33
33
  expect(parent_class.configuration['ten']).to be_nil
34
34
  end
35
35
 
36
- describe '#keys' do
37
- it "returns the union of this class's keys with any parent keys" do
36
+ it "does not inherit nonheritable keys" do
37
+ expect(parent_class.configuration['nonheritable']).to be_nil
38
+ parent_class.configuration.nonheritable! :nonheritable
39
+ parent_class.configuration['nonheritable'] = "parent"
40
+ expect(parent_class.configuration['nonheritable']).to eq "parent"
41
+
42
+ subclass = Class.new(parent_class)
43
+ expect(subclass.configuration['nonheritable']).to be_nil
44
+
45
+ subclass.configuration['nonheritable'] = "child"
46
+
47
+ subsubclass = Class.new(subclass)
48
+ expect(subsubclass.configuration['nonheritable']).to be_nil
49
+ end
50
+
51
+ describe '#keys and #to_h' do
52
+ let(:subclass) { Class.new(parent_class) }
53
+ let(:subsubclass) { Class.new(subclass) }
54
+
55
+ before do
38
56
  parent_class.configuration['1'] = :a
39
57
  parent_class.configuration['2'] = :b
40
58
 
41
- subclass = Class.new(parent_class)
42
59
  subclass.configuration['2'] = :c
43
60
  subclass.configuration['3'] = :d
44
61
 
45
- subsubclass = Class.new(subclass)
46
62
  subsubclass.configuration['3'] = :e
47
63
  subsubclass.configuration['4'] = :f
64
+ end
48
65
 
66
+ it "returns the union of this class's keys with any parent keys" do
49
67
  expect(parent_class.configuration.keys).to eq ['1', '2']
68
+ expect(parent_class.configuration.to_h).to eq({ '1' => :a, '2' => :b })
50
69
  expect(subclass.configuration.keys).to eq ['1', '2', '3']
70
+ expect(subclass.configuration.to_h).to eq({ '1' => :a, '2' => :c, '3' => :d })
51
71
  expect(subsubclass.configuration.keys).to eq ['1', '2', '3', '4']
72
+ expect(subsubclass.configuration.to_h).to eq({ '1' => :a, '2' => :c, '3' => :e, '4' => :f })
73
+
74
+ # it doesn't mutate storage
75
+ subclass.configuration.to_h['1'] = :new
76
+ subclass.configuration.to_h['2'] = :new
77
+ expect(subclass.configuration['1']).to eq :a
78
+ expect(subclass.configuration['2']).to eq :c
79
+ expect(parent_class.configuration['1']).to eq :a
80
+ expect(parent_class.configuration['2']).to eq :b
52
81
 
53
82
  expect(parent_class.configuration['1']).to eq :a
54
83
  expect(parent_class.configuration['2']).to eq :b
@@ -62,6 +91,64 @@ describe Brainstem::Concerns::InheritableConfiguration do
62
91
  expect(subsubclass.configuration['3']).to eq :e
63
92
  expect(subsubclass.configuration['4']).to eq :f
64
93
  end
94
+
95
+ it "does not return nonheritable keys in the parent" do
96
+ parent_class.configuration.nonheritable! :nonheritable
97
+ parent_class.configuration['nonheritable'] = "why yes, I am nonheritable"
98
+ expect(subclass.configuration.keys).not_to include 'nonheritable'
99
+
100
+ expect(subclass.configuration.to_h.keys).not_to include 'nonheritable'
101
+ expect(subclass.configuration.has_key?('nonheritable')).to eq false
102
+ end
103
+ end
104
+
105
+ describe "#fetch" do
106
+ let(:config) { parent_class.configuration }
107
+ let(:my_block) { Proc.new { nil } }
108
+
109
+ before do
110
+ parent_class.configuration["my_key"] = "yep"
111
+ end
112
+
113
+ context "when key is found" do
114
+ it "returns the key" do
115
+ expect(config.fetch("my_key")).to eq "yep"
116
+ end
117
+ end
118
+
119
+ context "when key is not found" do
120
+ context "when default or block not given" do
121
+ it "raises a KeyError exception" do
122
+ expect { config.fetch("fake_key") }.to raise_exception KeyError
123
+ end
124
+ end
125
+
126
+ context "when block given" do
127
+ before do
128
+ mock(my_block).call { "hey" }
129
+ end
130
+
131
+ it "evals and returns the block" do
132
+ expect(config.fetch("fake_key", &my_block)).to eq "hey"
133
+ end
134
+ end
135
+
136
+ context "when default given" do
137
+ it "returns the default" do
138
+ expect(config.fetch("fake_key", "hey")).to eq "hey"
139
+ end
140
+ end
141
+
142
+ context "when default and block given" do
143
+ before do
144
+ mock(my_block).call("hey") { "sup" }
145
+ end
146
+
147
+ it "evals and returns the block, passing it the default" do
148
+ expect(config.fetch("fake_key", "hey", &my_block)).to eq "sup"
149
+ end
150
+ end
151
+ end
65
152
  end
66
153
 
67
154
  describe '#nest!' do
@@ -199,6 +286,19 @@ describe Brainstem::Concerns::InheritableConfiguration do
199
286
  expect(lambda { subclass.configuration['list'] = 2 }).to raise_error('You cannot override an inheritable array once set')
200
287
  end
201
288
  end
289
+
290
+ describe "#nonheritable!" do
291
+ it "adds the key to the nonheritable attributes list" do
292
+ parent_class.configuration.nonheritable! :nonheritable
293
+ expect(parent_class.configuration.nonheritable_keys.to_a).to eq ["nonheritable"]
294
+ end
295
+
296
+ it "dedupes" do
297
+ parent_class.configuration.nonheritable! :nonheritable
298
+ parent_class.configuration.nonheritable! :nonheritable
299
+ expect(parent_class.configuration.nonheritable_keys.to_a).to eq ["nonheritable"]
300
+ end
301
+ end
202
302
  end
203
303
 
204
304
  describe '#configuration' do
@@ -0,0 +1,48 @@
1
+ require 'spec_helper'
2
+ require 'brainstem/concerns/optional'
3
+
4
+ describe Brainstem::Concerns::Optional do
5
+ let(:optional_class) do
6
+ Class.new do
7
+ include Brainstem::Concerns::Optional
8
+ end
9
+ end
10
+
11
+ context "when options are not passed" do
12
+ it "raises no error" do
13
+ expect { optional_class.new }.not_to raise_error
14
+ end
15
+ end
16
+
17
+ context "when options are passed" do
18
+ context "when option is whitelisted" do
19
+ before do
20
+ stub.any_instance_of(optional_class).valid_options { [:thing] }
21
+ end
22
+
23
+ context "when an accessor exists" do
24
+ before do
25
+ mock.any_instance_of(optional_class).thing=("blah")
26
+ end
27
+
28
+ it "passes the option to the accessor" do
29
+ optional_class.new(thing: "blah")
30
+ end
31
+ end
32
+
33
+ context "when no accessor exists" do
34
+ it "raises an error" do
35
+ expect { optional_class.new(thing: "blah") }.to \
36
+ raise_error NoMethodError
37
+ end
38
+ end
39
+ end
40
+
41
+ context "when option is not whitelisted" do
42
+ it "does not send the symbol" do
43
+ dont_allow.any_instance_of(optional_class).other_thing
44
+ expect { optional_class.new(other_thing: "nope") }.not_to raise_error
45
+ end
46
+ end
47
+ end
48
+ end
@@ -5,11 +5,17 @@ require 'brainstem/concerns/presenter_dsl'
5
5
  #
6
6
  # brainstem_key :projects
7
7
  #
8
+ # title "Project"
9
+ # description "It does stuff"
10
+ #
11
+ #
8
12
  # conditionals do
9
- # model :title_is_hello, lambda { workspace.title == 'hello' }, 'visible when the title is hello'
10
- # request :user_is_bob, lambda { current_user.username == 'bob' }, 'visible only to bob'
13
+ # model :title_is_hello, lambda { workspace.title == 'hello' }, info: 'visible when the title is hello'
14
+ # request :user_is_bob, lambda { current_user.username == 'bob' }, info: 'visible only to bob'
11
15
  # end
12
16
  #
17
+ # sort_order :created_at, ".created_at"
18
+ #
13
19
  # fields do
14
20
  # field :title, :string
15
21
  # field :description, :string
@@ -20,7 +26,8 @@ require 'brainstem/concerns/presenter_dsl'
20
26
  # if: [:user_is_bob, :title_is_hello]
21
27
  #
22
28
  # with_options if: :user_is_bob do
23
- # field :bob_title, :string, 'another name for the title, only for Bob',
29
+ # field :bob_title, :string,
30
+ # info: 'another name for the title, only for Bob',
24
31
  # via: :title
25
32
  # end
26
33
  # fields :nested_permissions do
@@ -30,10 +37,13 @@ require 'brainstem/concerns/presenter_dsl'
30
37
  # end
31
38
  #
32
39
  # associations do
33
- # association :tasks, Task, 'The Tasks in this Workspace',
40
+ # association :tasks, Task,
41
+ # info: 'The Tasks in this Workspace',
34
42
  # restrict_to_only: true
35
- # association :lead_user, User, 'The user who runs this Workspace'
36
- # association :subtasks, Task, 'Only Tasks in this Workspace that are subtasks',
43
+ # association :lead_user, User,
44
+ # info: 'The user who runs this Workspace'
45
+ # association :subtasks, Task,
46
+ # info: 'Only Tasks in this Workspace that are subtasks',
37
47
  # dynamic: lambda { |workspace| workspace.tasks.where('parent_id IS NOT NULL') }
38
48
  # association :something, :polymorphic
39
49
  # end
@@ -62,11 +72,99 @@ describe Brainstem::Concerns::PresenterDSL do
62
72
  end
63
73
  end
64
74
 
75
+ describe 'the title method' do
76
+ it "is stored in the configuration" do
77
+ presenter_class.title "Project"
78
+ expect(presenter_class.configuration[:title][:info]).to eq "Project"
79
+ end
80
+
81
+ it "stores options" do
82
+ presenter_class.title "Project", nodoc: true
83
+ expect(presenter_class.configuration[:title][:nodoc]).to be true
84
+ end
85
+
86
+ it "is not inherited" do
87
+ presenter_class.title "Project"
88
+ subclass = Class.new(presenter_class)
89
+ expect(subclass.configuration).not_to have_key :title
90
+ end
91
+ end
92
+
93
+ describe 'the description method' do
94
+ it "is stored in the configuration" do
95
+ presenter_class.description "desc 123"
96
+ expect(presenter_class.configuration[:description][:info]).to eq "desc 123"
97
+ end
98
+
99
+ it "stores options" do
100
+ presenter_class.description "Project", nodoc: true
101
+ expect(presenter_class.configuration[:description][:nodoc]).to be true
102
+ end
103
+
104
+ it "is not inherited" do
105
+ presenter_class.description "desc 123"
106
+ subclass = Class.new(presenter_class)
107
+ expect(subclass.configuration).not_to have_key :description
108
+ end
109
+ end
110
+
111
+ describe 'the nodoc! method' do
112
+ before do
113
+ presenter_class.nodoc!
114
+ end
115
+
116
+ it "is stored in the configuration" do
117
+ expect(presenter_class.configuration[:nodoc]).to be true
118
+ end
119
+ end
120
+
121
+
122
+ # sort_order :created_at, ".created_at"
123
+ describe 'the sort_order block' do
124
+ let(:value) { "widgets.created_at" }
125
+ let(:orders) { presenter_class.configuration[:sort_orders] }
126
+
127
+ context "when passed value is a column" do
128
+ before do
129
+ presenter_class.sort_order :created_at, value, info: "sorts by creation time"
130
+ end
131
+
132
+ it "is stored in the configuration by name" do
133
+ expect(orders).to have_key "created_at"
134
+ end
135
+
136
+ it "stores the value passed" do
137
+ expect(orders[:created_at][:value]).to eq "widgets.created_at"
138
+ end
139
+
140
+ it "stores the documentation" do
141
+ expect(orders[:created_at][:info]).to eq "sorts by creation time"
142
+ end
143
+ end
144
+
145
+ context "when passed value is a block" do
146
+ let(:value) { Proc.new { nil } }
147
+
148
+ before do
149
+ presenter_class.sort_order :created_at, info: "sorts by creation time", &value
150
+ end
151
+
152
+ it "stores the value passed" do
153
+ expect(orders[:created_at][:value]).to eq value
154
+ end
155
+
156
+ it "stores the documentation" do
157
+ expect(orders[:created_at][:info]).to eq "sorts by creation time"
158
+ end
159
+ end
160
+ end
161
+
162
+
65
163
  describe 'the conditional block' do
66
164
  before do
67
165
  presenter_class.conditionals do
68
- model :title_is_hello, lambda { |workspace| workspace.title == 'hello' }, 'visible when the title is hello'
69
- request :user_is_bob, lambda { current_user == 'bob' }, 'visible only to bob'
166
+ model :title_is_hello, lambda { |workspace| workspace.title == 'hello' }, info: 'visible when the title is hello', nodoc: true
167
+ request :user_is_bob, lambda { current_user == 'bob' }, info: 'visible only to bob'
70
168
  end
71
169
  end
72
170
 
@@ -75,6 +173,7 @@ describe Brainstem::Concerns::PresenterDSL do
75
173
  expect(presenter_class.configuration[:conditionals][:title_is_hello].action).to be_present
76
174
  expect(presenter_class.configuration[:conditionals][:title_is_hello].type).to eq :model
77
175
  expect(presenter_class.configuration[:conditionals][:title_is_hello].description).to eq 'visible when the title is hello'
176
+ expect(presenter_class.configuration[:conditionals][:title_is_hello].options).to eq({ nodoc: true, info: 'visible when the title is hello' })
78
177
  expect(presenter_class.configuration[:conditionals][:user_is_bob].action).to be_present
79
178
  expect(presenter_class.configuration[:conditionals][:user_is_bob].type).to eq :request
80
179
  expect(presenter_class.configuration[:conditionals][:user_is_bob].description).to eq 'visible only to bob'
@@ -83,13 +182,30 @@ describe Brainstem::Concerns::PresenterDSL do
83
182
  it 'is inherited and overridable' do
84
183
  subclass = Class.new(presenter_class)
85
184
  subclass.conditionals do
86
- model :silly_conditional, lambda { rand > 0.5 }, 'visible half the time'
87
- model :title_is_hello, lambda { |workspace| workspace.title == 'HELLO' }, 'visible when the title is hello (in all caps)'
185
+ model :silly_conditional, lambda { rand > 0.5 }, info: 'visible half the time'
186
+ model :title_is_hello, lambda { |workspace| workspace.title == 'HELLO' }, info: 'visible when the title is hello (in all caps)'
88
187
  end
89
188
  expect(presenter_class.configuration[:conditionals].keys).to eq %w[title_is_hello user_is_bob]
90
189
  expect(subclass.configuration[:conditionals].keys).to eq %w[title_is_hello user_is_bob silly_conditional]
91
190
  expect(presenter_class.configuration[:conditionals][:title_is_hello].description).to eq "visible when the title is hello"
191
+ expect(presenter_class.configuration[:conditionals][:title_is_hello].options).to eq({ nodoc: true, info: "visible when the title is hello" })
92
192
  expect(subclass.configuration[:conditionals][:title_is_hello].description).to eq "visible when the title is hello (in all caps)"
193
+ expect(subclass.configuration[:conditionals][:title_is_hello].options).to eq({ info: "visible when the title is hello (in all caps)" })
194
+ end
195
+
196
+ context 'when options hash is a hash with indifferent access' do
197
+ before do
198
+ presenter_class.conditionals do
199
+ request :user_is_jane, lambda { current_user == 'jane' }, { info: 'visible only to Jane' }.with_indifferent_access
200
+ end
201
+ end
202
+
203
+ it 'is stored in the configuration correctly' do
204
+ expect(presenter_class.configuration[:conditionals].keys).to include('user_is_jane')
205
+ expect(presenter_class.configuration[:conditionals][:user_is_jane].action).to be_present
206
+ expect(presenter_class.configuration[:conditionals][:user_is_jane].type).to eq :request
207
+ expect(presenter_class.configuration[:conditionals][:user_is_jane].description).to eq 'visible only to Jane'
208
+ end
93
209
  end
94
210
  end
95
211
 
@@ -103,7 +219,8 @@ describe Brainstem::Concerns::PresenterDSL do
103
219
  if: [:user_is_bob, :title_is_hello]
104
220
 
105
221
  with_options if: :user_is_bob do
106
- field :bob_title, :string, 'another name for the title, only for Bob',
222
+ field :bob_title, :string,
223
+ info: 'another name for the title, only for Bob',
107
224
  via: :title
108
225
  end
109
226
  fields :nested_permissions do
@@ -125,7 +242,11 @@ describe Brainstem::Concerns::PresenterDSL do
125
242
  expect(presenter_class.configuration[:fields][:secret].options).to eq({ via: :secret_info, if: [:user_is_bob, :title_is_hello] })
126
243
  expect(presenter_class.configuration[:fields][:bob_title].type).to eq :string
127
244
  expect(presenter_class.configuration[:fields][:bob_title].description).to eq 'another name for the title, only for Bob'
128
- expect(presenter_class.configuration[:fields][:bob_title].options).to eq({ via: :title, if: [:user_is_bob] })
245
+ expect(presenter_class.configuration[:fields][:bob_title].options).to eq(
246
+ via: :title,
247
+ if: [:user_is_bob],
248
+ info: 'another name for the title, only for Bob'
249
+ )
129
250
  end
130
251
 
131
252
  it 'handles nesting' do
@@ -139,7 +260,7 @@ describe Brainstem::Concerns::PresenterDSL do
139
260
  subclass.fields do
140
261
  field :title, :string
141
262
  with_options if: [:some_condition, :some_other_condition] do
142
- field :updated_at, :datetime, 'this time I have a description and condition'
263
+ field :updated_at, :datetime, info: 'this time I have a description and condition'
143
264
  end
144
265
  end
145
266
  expect(presenter_class.configuration[:fields].keys).to match_array %w[updated_at dynamic_title secret bob_title nested_permissions]
@@ -147,24 +268,32 @@ describe Brainstem::Concerns::PresenterDSL do
147
268
  expect(presenter_class.configuration[:fields][:updated_at].description).to be_nil
148
269
  expect(presenter_class.configuration[:fields][:updated_at].options).to eq({})
149
270
  expect(subclass.configuration[:fields][:updated_at].description).to eq 'this time I have a description and condition'
150
- expect(subclass.configuration[:fields][:updated_at].options).to eq({ if: [:some_condition, :some_other_condition] })
271
+ expect(subclass.configuration[:fields][:updated_at].options).to eq(
272
+ if: [:some_condition, :some_other_condition],
273
+ info: 'this time I have a description and condition'
274
+ )
151
275
  end
152
276
 
153
277
  it 'any :if options are combined and inherited using with_options' do
154
278
  presenter_class.fields do
155
279
  with_options if: :user_is_bob do
156
- field :bob_title, :string, 'another name for the title, only for Bob',
157
- via: :title, if: :another_condition
158
- field :bob_title2, :string, 'another name for the title, only for Bob',
280
+ field :bob_title, :string,
281
+ info: 'another name for the title, only for Bob',
282
+ via: :title,
283
+ if: :another_condition
284
+ field :bob_title2, :string,
285
+ info: 'another name for the title, only for Bob',
159
286
  via: :title, if: :another_condition
160
287
  end
161
288
  end
162
289
  subclass = Class.new(presenter_class)
163
290
  subclass.fields do
164
291
  with_options if: [:user_is_bob, :more_specific] do
165
- field :bob_title, :string, 'another name for the title, only for Bob',
292
+ field :bob_title, :string,
293
+ info: 'another name for the title, only for Bob',
166
294
  via: :title, if: [:another_condition]
167
- field :bob_title2, :string, 'another name for the title, only for Bob',
295
+ field :bob_title2, :string,
296
+ info: 'another name for the title, only for Bob',
168
297
  via: :title
169
298
  end
170
299
  end
@@ -206,14 +335,30 @@ describe Brainstem::Concerns::PresenterDSL do
206
335
  expect(subclass.configuration[:fields][:nested_permissions][:deeper][:something]).to be_present
207
336
  expect(subclass.configuration[:fields][:new_nested_permissions]).to be_present
208
337
  end
338
+
339
+ context "when options is a hash with indifferent access" do
340
+ before do
341
+ presenter_class.fields do
342
+ field :synced_at, :datetime, { info: "Last time the object was synced" }.with_indifferent_access
343
+ end
344
+ end
345
+
346
+ it "is stored in the configuration correctly" do
347
+ expect(presenter_class.configuration[:fields].keys).to include('synced_at')
348
+ expect(presenter_class.configuration[:fields][:synced_at].type).to eq :datetime
349
+ expect(presenter_class.configuration[:fields][:synced_at].description).to eq 'Last time the object was synced'
350
+ end
351
+ end
209
352
  end
210
353
 
211
354
  describe 'the associations block' do
212
355
  before do
213
356
  presenter_class.associations do
214
- association :tasks, Task, 'The Tasks in this Workspace',
357
+ association :tasks, Task,
358
+ info: 'The Tasks in this Workspace',
215
359
  restrict_to_only: true
216
- association :subtasks, Task, 'Only Tasks in this Workspace that are subtasks',
360
+ association :subtasks, Task,
361
+ info: 'Only Tasks in this Workspace that are subtasks',
217
362
  dynamic: lambda { |workspace| workspace.tasks.where('parent_id IS NOT NULL') }
218
363
  association :something, :polymorphic
219
364
  end
@@ -223,10 +368,10 @@ describe Brainstem::Concerns::PresenterDSL do
223
368
  expect(presenter_class.configuration[:associations].keys).to match_array %w[tasks subtasks something]
224
369
  expect(presenter_class.configuration[:associations][:tasks].target_class).to eq Task
225
370
  expect(presenter_class.configuration[:associations][:tasks].description).to eq 'The Tasks in this Workspace'
226
- expect(presenter_class.configuration[:associations][:tasks].options).to eq({ restrict_to_only: true })
371
+ expect(presenter_class.configuration[:associations][:tasks].options).to eq({ restrict_to_only: true, info: 'The Tasks in this Workspace' })
227
372
  expect(presenter_class.configuration[:associations][:subtasks].target_class).to eq Task
228
373
  expect(presenter_class.configuration[:associations][:subtasks].description).to eq 'Only Tasks in this Workspace that are subtasks'
229
- expect(presenter_class.configuration[:associations][:subtasks].options.keys).to eq [:dynamic]
374
+ expect(presenter_class.configuration[:associations][:subtasks].options.keys).to match_array [:dynamic, :info]
230
375
  expect(presenter_class.configuration[:associations][:something].target_class).to eq :polymorphic
231
376
  expect(presenter_class.configuration[:associations][:something].description).to be_nil
232
377
  end
@@ -234,20 +379,37 @@ describe Brainstem::Concerns::PresenterDSL do
234
379
  it 'is inherited and overridable' do
235
380
  subclass = Class.new(presenter_class)
236
381
  subclass.associations do
237
- association :tasks, Task, 'The Tasks in this Workspace'
238
- association :lead_user, User, 'The user who runs this Workspace'
382
+ association :tasks, Task, info: 'The Tasks in this Workspace'
383
+ association :lead_user, User, info: 'The user who runs this Workspace'
239
384
  end
240
385
 
241
386
  expect(presenter_class.configuration[:associations].keys).to match_array %w[tasks subtasks something]
242
387
  expect(subclass.configuration[:associations].keys).to match_array %w[tasks subtasks lead_user something]
243
388
 
244
- expect(presenter_class.configuration[:associations][:tasks].options).to eq({ restrict_to_only: true })
389
+ expect(presenter_class.configuration[:associations][:tasks].options).to eq({ restrict_to_only: true, info: 'The Tasks in this Workspace' })
245
390
  expect(presenter_class.configuration[:associations][:lead_user]).to be_nil
246
391
 
247
- expect(subclass.configuration[:associations][:tasks].options).to eq({})
392
+ expect(subclass.configuration[:associations][:tasks].options).to eq({ info: 'The Tasks in this Workspace' })
248
393
  expect(subclass.configuration[:associations][:lead_user].target_class).to eq User
249
394
  expect(subclass.configuration[:associations][:lead_user].description).to eq 'The user who runs this Workspace'
250
395
  end
396
+
397
+ context "when options is a hash with indifferent access" do
398
+ before do
399
+ presenter_class.associations do
400
+ association :something_else, :polymorphic, { info: 'The other things in this Workspace', restrict_to_only: true }.with_indifferent_access
401
+ end
402
+ end
403
+
404
+ it "is stored in the configuration correctly" do
405
+ expect(presenter_class.configuration[:associations].keys).to include('something_else')
406
+ expect(presenter_class.configuration[:associations][:something_else].description).to eq 'The other things in this Workspace'
407
+ expect(presenter_class.configuration[:associations][:something_else].options).to eq(
408
+ info: 'The other things in this Workspace',
409
+ restrict_to_only: true
410
+ )
411
+ end
412
+ end
251
413
  end
252
414
 
253
415
  describe ".helper" do
@@ -373,16 +535,25 @@ describe Brainstem::Concerns::PresenterDSL do
373
535
  end
374
536
 
375
537
  describe ".filter" do
538
+ let(:foo) { presenter_class.configuration[:filters][:foo] }
539
+
376
540
  it "creates an entry in the filters configuration" do
377
- presenter_class.filter(:foo, :default => true) { 1 }
378
- expect(presenter_class.configuration[:filters][:foo][0]).to eq({"default" => true})
379
- expect(presenter_class.configuration[:filters][:foo][1]).to be_a(Proc)
541
+ my_proc = Proc.new { 1 }
542
+ presenter_class.filter(:foo, :default => true, &my_proc)
543
+
544
+ expect(foo).to eq({ "default" => true, "value" => my_proc })
380
545
  end
381
546
 
382
547
  it "accepts names without blocks" do
383
548
  presenter_class.filter(:foo)
384
- expect(presenter_class.configuration[:filters][:foo][1]).to be_nil
549
+ expect(foo[:value]).to be_nil
550
+ end
551
+
552
+ it "records the info option" do
553
+ presenter_class.filter(:foo, :info => "This is documented.")
554
+ expect(foo[:info]).to eq "This is documented."
385
555
  end
556
+
386
557
  end
387
558
 
388
559
  describe ".search" do