edit_in_place 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +301 -0
- data/Rakefile +15 -0
- data/lib/edit_in_place.rb +72 -0
- data/lib/edit_in_place/builder.rb +161 -0
- data/lib/edit_in_place/configuration.rb +48 -0
- data/lib/edit_in_place/duplicate_registration_error.rb +6 -0
- data/lib/edit_in_place/extended_builder.rb +40 -0
- data/lib/edit_in_place/field_options.rb +84 -0
- data/lib/edit_in_place/field_type.rb +52 -0
- data/lib/edit_in_place/field_type_registrar.rb +22 -0
- data/lib/edit_in_place/invalid_field_type_error.rb +13 -0
- data/lib/edit_in_place/invalid_registration_name_error.rb +7 -0
- data/lib/edit_in_place/railtie.rb +6 -0
- data/lib/edit_in_place/registrar.rb +90 -0
- data/lib/edit_in_place/unregistered_field_type_error.rb +13 -0
- data/lib/edit_in_place/unsupported_mode_error.rb +13 -0
- data/lib/edit_in_place/version.rb +5 -0
- data/lib/tasks/edit_in_place_tasks.rake +5 -0
- metadata +170 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1a49b654229bb462de8d09e761fb800ef58647a8964b793e90c308e29b65589b
|
4
|
+
data.tar.gz: dbc63f81542231c4defe07042f10664cf0376a61f47a41abf2983175134671ba
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 615184175d7f147d9764c2a35ce3246e3114db24e33699ad02bfd422e8e6d9021653f2600ee0793245966e76538a8ccd286fd4e0c2aca361ddf29da279e5e76d
|
7
|
+
data.tar.gz: fef7dc43b4333a89fe8d23b3f62d92aabbca2372fffca8535899c6b089c21be8f64201621eea27c71c26f7e9aed54a7a97d4027963f69dc6519877ca6cc60218
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2021 Jacob
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,301 @@
|
|
1
|
+
# EditInPlace
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.com/jacoblockard99/edit_in_place.svg?branch=master)](https://travis-ci.com/jacoblockard99/edit_in_place)
|
4
|
+
[![Inline docs](http://inch-ci.org/github/jacoblockard99/edit_in_place.svg?branch=master)](http://inch-ci.org/github/jacoblockard99/edit_in_place)
|
5
|
+
[![Maintainability](https://api.codeclimate.com/v1/badges/389fc591cd9cccb2ceb1/maintainability)](https://codeclimate.com/github/jacoblockard99/edit_in_place/maintainability)
|
6
|
+
[![Test Coverage](https://api.codeclimate.com/v1/badges/389fc591cd9cccb2ceb1/test_coverage)](https://codeclimate.com/github/jacoblockard99/edit_in_place/test_coverage)
|
7
|
+
|
8
|
+
`edit_in_place` is a Rails plugin that facilitates the creation of user interfaces that allow the user to edit content "in place" in a natural way. `edit_in_place` aims to be:
|
9
|
+
- **Flexible.** Everything has been designed with extensibility in mind. The `edit_in_place` core (this repository) can be extended for a huge variety of use cases.
|
10
|
+
- **Reliable.** Every aspect of the plugin is thoroughly tested and documented.
|
11
|
+
- **Natural.** Coding with `edit_in_place` is just as natural as editing content with it is! We think you'll really enjoy working with the `edit_in_place` API.
|
12
|
+
|
13
|
+
## Links
|
14
|
+
|
15
|
+
- [API Docs](https://rubydoc.info/github/jacoblockard99/edit_in_place)
|
16
|
+
- [CHANGELOG.md](CHANGELOG.md)
|
17
|
+
- [Releases](https://github.com/jacoblockard99/edit_in_place/releases)
|
18
|
+
|
19
|
+
## Installation
|
20
|
+
|
21
|
+
`edit_in_place` is a Ruby gem. If you use Bundler, you may install it by adding it to your `Gemfile`, like so:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
gem 'edit_in_place'
|
25
|
+
```
|
26
|
+
|
27
|
+
And then execute:
|
28
|
+
```bash
|
29
|
+
$ bundle install
|
30
|
+
```
|
31
|
+
|
32
|
+
Or you may install it manually with:
|
33
|
+
```bash
|
34
|
+
$ gem install edit_in_place
|
35
|
+
```
|
36
|
+
|
37
|
+
`edit_in_place` currently has two dependencies: `rails` and `middlegem`.
|
38
|
+
|
39
|
+
## Concepts
|
40
|
+
|
41
|
+
`edit_in_place` is—at the most fundamental level—a tool to render content in different _modes_.
|
42
|
+
|
43
|
+
This begins with the concept of a _field_, which is a single, self-contained piece of modal content. Given the same input, but different modes, a field can be rendered differently. This functionality could be used in a variety of cases, but is especially useful for creating "editable content". As an example, imagine that we have built a static website. On that website, we would like to allow the owner to modify certain parts of it. The way we would approach this with `edit_in_place` is to make each editable portion of the page a field. Then, when the site is being viewed by a visitor, we render all the fields in a `:viewing` mode, but when being edited by the owner, an `:editing` mode. Each field is then rendered differently, given the appropriate mode. A "text" field, for example, might render plain text in a `:viewing` mode, but some kind of text input in an `:editing` mode.
|
44
|
+
|
45
|
+
A _field type_ is a subclass of `EditInPlace::FieldType` that is essentially a template for a field. Given various options, including a mode, it is in charge of actually rendering the field.
|
46
|
+
|
47
|
+
To render a field, the `EditInPlace::Builder#field` method is used. Given a field type, field options, and an input, it will merge the field options with the default ones, transform the input as appropriate, and use the `FieldType` to render the content, returning the result. This is where the flexibility of `edit_in_place` begins to reveal itself: you see, the "input" given to `#field` is completely arbitrary! In other words, you can set up field types to accept whatever input they need to render the field. That means that you have virtually limitless options for how exactly you acquire and save editable data—`edit_in_place` doesn't care.
|
48
|
+
|
49
|
+
Furthermore, `edit_in_place` has the concept of field "middlewares". You can pass middlewares ([`middlegem`](https://github.com/jacoblockard99/middlegem/) is used for middlewares) to `#field` that can arbitrarily transform its input. This can be used for a host of things—transforming the data, validating the input, adding arguments to the input based on context, or really anything you want.
|
50
|
+
|
51
|
+
Of course, with this power comes significant verbosity. With only the `edit_in_place` core, you would need to set everything up yourself. However, in many cases, editable content with `edit_in_place` follows a fairly set pattern: "models" (not necessarily ActiveRecord ones) store editable attribtues; an `edit_in_place` field corresponds to one (or occasionally more) attributes; and the "editable" version of a field is a form input which allows the data to be saved back to a data store. For this functionality, see the `edit_in_place` extension [`models_in_place`](https://github.com/jacoblockard99/models_in_place).
|
52
|
+
|
53
|
+
## Usage
|
54
|
+
|
55
|
+
### Field Types
|
56
|
+
|
57
|
+
A fair amount of configuration is required to get started with `edit_in_place` if you're not using `models_in_place`. The first step is to define some field types, which provide templates for generated fields. This can be done by extended the `FieldType` class and doing one of the following:
|
58
|
+
1. Overriding the `render` method, which is called regardless of the mode.
|
59
|
+
2. Relying on the default `render` implementation, and overriding one of the `render_*` that it will call.
|
60
|
+
Here are a few examples:
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
# text_field_type.rb
|
64
|
+
|
65
|
+
class TextFieldType < EditInPlace::FieldType
|
66
|
+
protected
|
67
|
+
|
68
|
+
def render_viewing(options, name, value)
|
69
|
+
options.view.tag.p value
|
70
|
+
end
|
71
|
+
|
72
|
+
def render_editing(options, name, value)
|
73
|
+
options.view.text_field_tag name, value
|
74
|
+
end
|
75
|
+
end
|
76
|
+
```
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
# boolean_field_type.rb
|
80
|
+
|
81
|
+
class DummyFieldtype < EditInPlace::FieldType
|
82
|
+
def render(options, *)
|
83
|
+
"You are currently #{options.mode} the webpage!"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
```
|
87
|
+
|
88
|
+
Notice that the `render` method is passed an options parameter, which is an instance of `EditInPlace::FieldOptions`, that contains the view context and the mode. Also note how `render_viewing` and `render_editing` will be called according to the current mode. If you want to define a `render_*` method for a different mode, you'll need to override the `supported_modes` method, like so:
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
class LockedField
|
92
|
+
protected
|
93
|
+
|
94
|
+
def render_viewing(*)
|
95
|
+
'You are viewing this field!'
|
96
|
+
end
|
97
|
+
|
98
|
+
def render_editing(*)
|
99
|
+
'You are editing this field!'
|
100
|
+
end
|
101
|
+
|
102
|
+
def render_admin_editing(*)
|
103
|
+
'You are editing this field as an admin!'
|
104
|
+
end
|
105
|
+
|
106
|
+
def supported_modes
|
107
|
+
[:viewing, :editing:, :admin_editing]
|
108
|
+
end
|
109
|
+
end
|
110
|
+
```
|
111
|
+
|
112
|
+
Attempts to call `render` with a mode that is not is `supported_modes` will result in an `EditInPlace::UnsupportedModeError`, even if the field type has a corresponding `render_*` method.
|
113
|
+
|
114
|
+
As mentioned earlier, one of the aims of `edit_in_place` is to be natural for the developer. Thus, in the interests of convenience, `edit_in_place` allows you to "register" field types with a name, making them easier to use. We might register our "text" field type, for example, like this:
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
EditInPlace.configure do |c|
|
118
|
+
c.field_types.register :text, TextFieldType.new
|
119
|
+
end
|
120
|
+
```
|
121
|
+
|
122
|
+
Now this:
|
123
|
+
|
124
|
+
```erb
|
125
|
+
<%= @builder.field TextFieldType.new, 'contact_name', 'Jacob' %>
|
126
|
+
```
|
127
|
+
|
128
|
+
Becomes:
|
129
|
+
|
130
|
+
```erb
|
131
|
+
<%= @builder.field :text, 'contact_name', 'Jacob' %>
|
132
|
+
```
|
133
|
+
|
134
|
+
Note that field type names must be symbols—strings are not allowed. Also note that duplicate registrations are not alowed and will raise an `EditInPlace::DuplicateRegistrationError`.
|
135
|
+
|
136
|
+
### Configuring a Builder
|
137
|
+
|
138
|
+
The next step is creating and using an `EditInPlace::Builder` instance. A builder always has an `EditInPlace::Configuration` instance that contains all its options and context. When a builder is first instantiated, its configuration is copied from the global `EditInPlace` configuration. Thus, you can set any global configuration options using `EditInPlace.config` or `EditInPlace.configure`, and all builders with use those options by default. For example:
|
139
|
+
|
140
|
+
```ruby
|
141
|
+
EditInPlace.configure do |c|
|
142
|
+
c.field_options.mode = :editing
|
143
|
+
end
|
144
|
+
```
|
145
|
+
|
146
|
+
This would make editing the *default* mode. Then, you can modify builder-specific configuration using `Builder#config` or `Builder#configure`, like so:
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
@builder = Builder.new # current mode is :editing
|
150
|
+
@builder.config.field_options.mode = :viewing # switched to :viewing
|
151
|
+
```
|
152
|
+
|
153
|
+
Both `EditInPlace.config` and `Builder#config` are `EditInPlace::Configuration` instances, so you configure them identically. You are encouraged to check out the [docs](https://rubydoc.info/github/jacoblockard99/edit_in_place/EditInPlace/Configuration) on `Configuration` to see all the available configuration options.
|
154
|
+
|
155
|
+
Only two options are really critical to using the builder: the view context and the mode. The view context is necessary when the field type needs access to a view context to render the field. In our `TextFieldType` example above, for example, the `text_field_tag` method was required. Probably the easiest way to pass the view context is to simply use the `view_context` method in Rails controllers. For example:
|
156
|
+
|
157
|
+
```
|
158
|
+
class SomeController
|
159
|
+
def index
|
160
|
+
@builder = Builder.new
|
161
|
+
@builder.field_options.view = view_context
|
162
|
+
end
|
163
|
+
end
|
164
|
+
```
|
165
|
+
|
166
|
+
Now, field types will automatically have access to the view context. This method has some pitfalls, however, most importantly that `view_context` returns a _new_ view context, not the one used in the actual view. If you must have the actual view context object, something like this could be done instead:
|
167
|
+
|
168
|
+
```erb
|
169
|
+
<% @builder.field_options.view = self %>
|
170
|
+
|
171
|
+
<%= @builder.field :text, "hello, world" %>
|
172
|
+
```
|
173
|
+
|
174
|
+
Of course, you would need to put this at the top of all your views.
|
175
|
+
|
176
|
+
There are a few approaches to managing builder modes. One approach is to have a seperate controller for each, like so:
|
177
|
+
|
178
|
+
```ruby
|
179
|
+
class ViewingController < ApplicationController
|
180
|
+
def index
|
181
|
+
@builder = Builder.new
|
182
|
+
|
183
|
+
render 'some_page'
|
184
|
+
end
|
185
|
+
end
|
186
|
+
```
|
187
|
+
|
188
|
+
```ruby
|
189
|
+
class EditingController < ApplicationController
|
190
|
+
def index
|
191
|
+
@builder = Builder.new
|
192
|
+
@builder.config.field_options.mode = :editing
|
193
|
+
|
194
|
+
render 'some_page'
|
195
|
+
end
|
196
|
+
end
|
197
|
+
```
|
198
|
+
|
199
|
+
Then, in the 'some_page' view you can use the builder in the exact same way, but get different results because of the different mode. Other options are possible, however. Use whatever works best!
|
200
|
+
|
201
|
+
### Rendering Fields
|
202
|
+
|
203
|
+
Once you have some fields types and a builder, you are ready to actually render some fields! The `Builder#field` method is used to render a field of a given type, with the given options, and the given input. For example, with our previous `TextFieldType` example:
|
204
|
+
|
205
|
+
```erb
|
206
|
+
<%= @builder.field :text, 'phone_number', '(123) 456-7890' %>
|
207
|
+
```
|
208
|
+
|
209
|
+
When viewing the site, the user would see a simple paragraph containing the phone number. When editing the site, the user would see an editable text input. Of course, the text input currently would do nothing—you are in charge of ensuring that the data actually gets saved. You can do this however you want, but typically the fields are submitted via a form or via AJAX to a controller which saves the data. As part of the philosphy of flexibility, `edit_in_place` makes no attempt whatsoever to handle any of this.
|
210
|
+
|
211
|
+
If the second argument to `Builder#field` is either a `FieldOptions` instance or a hash, then it will be used as the options for the field. For example, you could render a specific field as always editable:
|
212
|
+
|
213
|
+
```erb
|
214
|
+
<%= @builder.field :text, { mode: :editing }, 'phone_number', '(123) 456-7890' %>
|
215
|
+
```
|
216
|
+
|
217
|
+
If your field type happens to expect the first input argument to be a hash, you will need to pass an empty options hash, like this:
|
218
|
+
|
219
|
+
```erb
|
220
|
+
<%= @builder.field :some_type, {}, { option: true }, 'etc' %>
|
221
|
+
```
|
222
|
+
|
223
|
+
### Middlewares
|
224
|
+
|
225
|
+
Perhaps the most powerful feature of `edit_in_place` are the field middlewares. `edit_in_place` uses [`middlegem`](https://github/jacoblockard99/middlegem) for middlewares, which you may want to briefly review. Field middlewares allow the inputs to a field to be transformed before actually making it to the field type's `render` method. The use cases for this are limitless.
|
226
|
+
|
227
|
+
There are two steps for using field middlewares: defining them and using them. First, you must define all the middleware classes that you will be using in your application. This is to ensure that middlewares are always run in the right order. For example, let's say we have two middleware classes, one that multiplies a number by 10, and another that surrounds it with parentheses. We would define the middleware as follows:
|
228
|
+
|
229
|
+
```ruby
|
230
|
+
EditInPlace.configure do |c|
|
231
|
+
c.defined_middleware = [
|
232
|
+
MultiplyMiddleware,
|
233
|
+
ParenthesesMiddleware
|
234
|
+
]
|
235
|
+
```
|
236
|
+
|
237
|
+
This will ensure that, no matter what order they're added, the `MultplyMiddleware` will always be run before the `ParenthesesMiddleware`. If a middleware is used that has not been defined, a `Middlegem::UnpermittedMiddleware` error will be raised by `middlegem`.
|
238
|
+
|
239
|
+
Next, the middlewares can be actually used. Middlewares reside in the `FieldOptions` class, meaning you have three options for adding middlewares to fields: adding it to the global `EditInPlace` configuration, adding it to the `Builder` configuration, and passing them to `Builder#field`. You might want all fields to be surrounded in parentheses, for example, but only some to be multiplied by ten. This could be accomplished with:
|
240
|
+
|
241
|
+
```ruby
|
242
|
+
class SomeController
|
243
|
+
def index
|
244
|
+
@builder = Builder.new
|
245
|
+
@builder.config.field_options.middlewares << ParenthesesMiddleware.new
|
246
|
+
end
|
247
|
+
end
|
248
|
+
```
|
249
|
+
|
250
|
+
```erb
|
251
|
+
<%= @builder.field :text, { middlewares: [MultiplyMiddleware.new] }, 'name', '500' %>
|
252
|
+
<%= @builder.field :text, 'name', 'a string' %>
|
253
|
+
```
|
254
|
+
|
255
|
+
Now, when viewing the site, the first one would show "(5000)" and the second would show "(a string)". Note that middlewares are often too verbose to be easily used like this. They are really best used by edit_in_place extensions.
|
256
|
+
|
257
|
+
### Scoped Builders
|
258
|
+
|
259
|
+
There will likely be times when you wish for many fields to have the same `FieldOptions`. This can be accomplished with the `Builder#scoped` method, which allows field options to be shared across a block. For example:
|
260
|
+
|
261
|
+
```erb
|
262
|
+
<%= @builder.scoped middlewares: [AppendArgumentMiddleware.new('example')] |b| do %>
|
263
|
+
<%= b.field :text, 'name', 'value' %>
|
264
|
+
<% end %>
|
265
|
+
```
|
266
|
+
|
267
|
+
Now any fields generated with the scoped `b` builder will have an `'example'` argument appended to their input (assuming that `AppendArgumentMiddleware` has been defined).
|
268
|
+
|
269
|
+
### Extending Builder
|
270
|
+
|
271
|
+
If you are developing an extension for `edit_in_place`, you may wish to add methods to `Builder`. While you could create a subclass, subclassing has its share of problems. You could not, for example, use multiple builder subclasses at once. Thus, `edit_in_place` provides an `ExtendedBuilder` class which you can use to add **new** methods to `Builder`. Essentially, `ExtendedBuilder` stores a base builder and delegates all missing method calls to it. You can use it like:
|
272
|
+
|
273
|
+
```ruby
|
274
|
+
class MyBuilderExtension < EditInPlace::ExtendedBuilder
|
275
|
+
def hello
|
276
|
+
'Hello, world!'
|
277
|
+
end
|
278
|
+
end
|
279
|
+
```
|
280
|
+
|
281
|
+
And then instantiate it:
|
282
|
+
|
283
|
+
```ruby
|
284
|
+
class SomeController < ApplicationController
|
285
|
+
def index
|
286
|
+
base = Builder.new
|
287
|
+
@my_builder = MyBuilderExtension.new(base)
|
288
|
+
end
|
289
|
+
end
|
290
|
+
```
|
291
|
+
|
292
|
+
Then, in the view:
|
293
|
+
|
294
|
+
```erb
|
295
|
+
<%= @my_builder.hello %>
|
296
|
+
```
|
297
|
+
|
298
|
+
These builder extension can be chained as many times as desired. Please note however that *you should only add new methods*, never override existing ones. If you override existing methods, further down the chain, that method may be called, and will not be sent to your extension, which could cause some confusing results.
|
299
|
+
|
300
|
+
## License
|
301
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
|
5
|
+
require 'bundler/gem_tasks'
|
6
|
+
|
7
|
+
require 'rake/testtask'
|
8
|
+
|
9
|
+
Rake::TestTask.new(:test) do |t|
|
10
|
+
t.libs << 'test'
|
11
|
+
t.pattern = 'test/**/*_test.rb'
|
12
|
+
t.verbose = false
|
13
|
+
end
|
14
|
+
|
15
|
+
task default: :test
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'edit_in_place/version'
|
4
|
+
require 'edit_in_place/railtie'
|
5
|
+
require 'edit_in_place/builder'
|
6
|
+
require 'edit_in_place/configuration'
|
7
|
+
require 'edit_in_place/registrar'
|
8
|
+
require 'edit_in_place/extended_builder'
|
9
|
+
require 'edit_in_place/field_options'
|
10
|
+
require 'edit_in_place/field_type'
|
11
|
+
require 'edit_in_place/field_type_registrar'
|
12
|
+
|
13
|
+
# {EditInPlace} is a namespace that contains all the modules and classes of the edit_in_place
|
14
|
+
# Rails gemified plugin.
|
15
|
+
#
|
16
|
+
# @author Jacob Lockard
|
17
|
+
# @since 0.1.0
|
18
|
+
module EditInPlace
|
19
|
+
# {Error} is a subclass of {https://ruby-doc.org/core-2.5.0/StandardError.html StandardError}
|
20
|
+
# from which all custom errors in edit_in_place are derived. One potential use for this class
|
21
|
+
# is to rescue all custom errors produced by edit_in_place. For example:
|
22
|
+
#
|
23
|
+
# begin
|
24
|
+
# # Do something risky with edit_in_place here...
|
25
|
+
# rescue EditInPlace::Error
|
26
|
+
# # Catch any edit_in_place-specific error here...
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# @see https://ruby-doc.org/core-2.0.0/Exception.html
|
30
|
+
class Error < StandardError; end
|
31
|
+
|
32
|
+
@config = Configuration.new
|
33
|
+
|
34
|
+
# Gets the `Configuration` instance that represents the global configuration for the
|
35
|
+
# edit_in_place plugin. The global configuration will be applied to all created {Builder}
|
36
|
+
# instances.
|
37
|
+
# @return [Configuration] the global configuration.
|
38
|
+
# @see Configuration
|
39
|
+
def self.config
|
40
|
+
@config
|
41
|
+
end
|
42
|
+
|
43
|
+
# Sets the `Configuration` instance that represents the global configuration for the
|
44
|
+
# edit_in_place plugin. A convenient use for this method is to reset the global configuration
|
45
|
+
# by setting it to +EditInPlace::Configuration.new+.
|
46
|
+
# @param config [Configuration] the global configuration.
|
47
|
+
# @return [void]
|
48
|
+
# @see Configuration
|
49
|
+
def self.config=(config)
|
50
|
+
@config = config
|
51
|
+
end
|
52
|
+
|
53
|
+
# Configures the edit_in_place plugin by yielding the global configuration to the given block.
|
54
|
+
# This is a convenient way to configure the plugin. For example:
|
55
|
+
#
|
56
|
+
# EditInPlace.configure do |c|
|
57
|
+
# c.field_options.mode = :editing
|
58
|
+
# c.defined_middlewares = [SomeMiddleware, AnotherMiddleware]
|
59
|
+
# end
|
60
|
+
# @yieldparam config [Configuration] the {Configuration} instance of the edit_in_place plugin.
|
61
|
+
# @yieldreturn [void]
|
62
|
+
# @return [void]
|
63
|
+
# @see Configuration
|
64
|
+
def self.configure
|
65
|
+
yield config if block_given?
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
require 'edit_in_place/invalid_field_type_error'
|
70
|
+
require 'edit_in_place/unregistered_field_type_error'
|
71
|
+
require 'edit_in_place/invalid_registration_name_error'
|
72
|
+
require 'edit_in_place/duplicate_registration_error'
|
@@ -0,0 +1,161 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EditInPlace
|
4
|
+
# {Builder} is the class that provides the actual functionality to build and render editable
|
5
|
+
# content. This class will usually be instantiated in a controller and passed somehow to a
|
6
|
+
# view. The view can then use its methods to generate content. Note that when a {Builder} is
|
7
|
+
# created, it's {Configuration} is copied from the global configuration.
|
8
|
+
#
|
9
|
+
# This class can be extended by utilizing {ExtendedBuilder} to safely add additional
|
10
|
+
# functionality. This can be particularly helpful for edit_in_place extensions that would like
|
11
|
+
# to add other content generation methods without requiring the user to yield another builder
|
12
|
+
# to the view.
|
13
|
+
#
|
14
|
+
# @author Jacob Lockard
|
15
|
+
# @since 0.1.0
|
16
|
+
class Builder
|
17
|
+
# The {Configuration} instance that stores the options for this {Builder}.
|
18
|
+
# @return [Configuration] the configuration for this {Builder}.
|
19
|
+
# @note This configuration is initially derived from the global configuration defined in
|
20
|
+
# {EditInPlace.config}.
|
21
|
+
attr_accessor :config
|
22
|
+
|
23
|
+
# Creates a new instance of {Builder}.
|
24
|
+
def initialize
|
25
|
+
@config = EditInPlace.config.dup
|
26
|
+
end
|
27
|
+
|
28
|
+
# Creates a deep copy of this {Builder}, whose configuration can be safely modified.
|
29
|
+
# @return [Builder] a deep copy of this {Builder}.
|
30
|
+
def dup
|
31
|
+
b = Builder.new
|
32
|
+
b.config = config.dup
|
33
|
+
b
|
34
|
+
end
|
35
|
+
|
36
|
+
# Configures this {Builder} by yielding its configuration to the given block. For example,
|
37
|
+
#
|
38
|
+
# @builder = EditInPlace::Builder.new
|
39
|
+
# @builder.configure do |c|
|
40
|
+
# c.field_options.mode = :editing
|
41
|
+
# c.field_options.middlewares = [:one, :two]
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# Note that this method is simply a convenience method, and the above code is exactly
|
45
|
+
# equivalent to the following:
|
46
|
+
#
|
47
|
+
# @builder = EditInPlace::Builder.new
|
48
|
+
# @builder.config.field_options.mode = :editing
|
49
|
+
# @builder.config.field_options.middlewares = [:one, :two]
|
50
|
+
# @yieldparam config [Configuration] the {Configuration} instance associated with this
|
51
|
+
# {Builder}.
|
52
|
+
# @yieldreturn [void]
|
53
|
+
# @return [void]
|
54
|
+
# @see EditInPlace.configure
|
55
|
+
def configure
|
56
|
+
yield config if block_given?
|
57
|
+
end
|
58
|
+
|
59
|
+
# @overload field(type, options, *args)
|
60
|
+
# Renders a single field of the given type with the given field option and arguments
|
61
|
+
# to be passed to the field type renderer.
|
62
|
+
# @param type [FieldType, Symbol] the type of field to render, either an actual instance of
|
63
|
+
# {FieldType}, or the symbol name of a registered field type.
|
64
|
+
# @param options [FieldOptions, Hash] the field options to be used when rendering the
|
65
|
+
# field. These options are defined in {FieldOptions}. Either an actual instance of
|
66
|
+
# {FieldOptions} or a hash are acceptable.
|
67
|
+
# @param args [Array] the arguments to be passed to the field renderer.
|
68
|
+
# @return [String] the rendered field, as HTML.
|
69
|
+
# @overload field(type, *args)
|
70
|
+
# Renders a single field of the given type with the given arguments to be passed to the
|
71
|
+
# field type renderer. The default field options (as defined in {FieldOptions}) will be
|
72
|
+
# used.
|
73
|
+
# @param type [FieldType, Symbol] the type of field to render, either an actual instance of
|
74
|
+
# {FieldType}, or the symbol name of a registered field type.
|
75
|
+
# @param args [Array] the arguments to be passed to the field renderer.
|
76
|
+
# @return [String] the rendered field, as HTML.
|
77
|
+
def field(type, *args)
|
78
|
+
inject_field_options!(args)
|
79
|
+
args[0] = config.field_options.merge(args[0])
|
80
|
+
|
81
|
+
definition = Middlegem::ArrayDefinition.new(config.defined_middlewares)
|
82
|
+
stack = Middlegem::Stack.new(definition, middlewares: args[0].middlewares)
|
83
|
+
args = stack.call(*args)
|
84
|
+
|
85
|
+
type = evaluate_field_type(type)
|
86
|
+
type.render(*args)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Yields a new, scoped {Builder} with the given field options. This method is helpful when
|
90
|
+
# many fields require the same options. One use, for example, is to easily give field types
|
91
|
+
# access to the current view context, like so:
|
92
|
+
#
|
93
|
+
# <!-- some_view.html.erb -->
|
94
|
+
#
|
95
|
+
# <%= @builder.scoped view: self do |b|
|
96
|
+
# <%= b.field(:example_type, 'random scoped field') %>
|
97
|
+
# <!-- ... -->
|
98
|
+
# <% end %>
|
99
|
+
#
|
100
|
+
# Now all fields generated using +b+ will have access to +self+ as the view context in
|
101
|
+
# {FieldOptions#view}.
|
102
|
+
# @yieldparam scoped_builder [Builder] the new, scoped {Builder} instance.
|
103
|
+
# @yieldreturn [string] the output generated with the scoped builder.
|
104
|
+
# @param field_options [FieldOptions, Hash] the field options that the scoped builder should
|
105
|
+
# have. Note that these options will be merged (using {FieldOptions#merge!}) with the
|
106
|
+
# current ones. Either an actual {FieldOptions} instance or a hash of options are
|
107
|
+
# acceptable.
|
108
|
+
# @return [string] the output of the block.
|
109
|
+
def scoped(field_options = {})
|
110
|
+
field_options = FieldOptions.new(field_options) unless field_options.is_a? FieldOptions
|
111
|
+
|
112
|
+
scoped_builder = dup
|
113
|
+
scoped_builder.config.field_options.merge!(field_options)
|
114
|
+
|
115
|
+
yield scoped_builder
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
# Ensures that the first argument in the given list of arguments is a valid, appropriate
|
121
|
+
# {FieldOptions} instance for the list of arguments. In particular:
|
122
|
+
# - When the first argument is already an instance of {FieldOptions}, the argument list
|
123
|
+
# is not touched.
|
124
|
+
# - When the first argument is a hash, then it is converted to a
|
125
|
+
# {FieldOptions} instance.
|
126
|
+
# - Otherwise, the default {FieldOptions} instance is prepended to the argument list.
|
127
|
+
# @param args [Array] the raw arguments into which to inject the field options.
|
128
|
+
# @return [void]
|
129
|
+
def inject_field_options!(args)
|
130
|
+
options = args.first
|
131
|
+
|
132
|
+
return if options.is_a? FieldOptions
|
133
|
+
|
134
|
+
if options.is_a? Hash
|
135
|
+
args[0] = FieldOptions.new(options)
|
136
|
+
else
|
137
|
+
args.unshift(FieldOptions.new)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Gets an appropriate {FieldType} instance for the given raw field type argument. In
|
142
|
+
# particular:
|
143
|
+
# - When the input is already a FieldType instance, that instance is simply returned.
|
144
|
+
# - When the input is a symbol, attempts to find a registered field type associated with it.
|
145
|
+
# - Otherwise, raises an error.
|
146
|
+
# @return [FieldType] an appropriate {FieldType} instance for the given input.
|
147
|
+
def evaluate_field_type(type)
|
148
|
+
case type
|
149
|
+
when FieldType
|
150
|
+
type
|
151
|
+
when Symbol
|
152
|
+
result = config.field_types.find(type)
|
153
|
+
raise UnregisteredFieldTypeError, type if result.nil?
|
154
|
+
|
155
|
+
result
|
156
|
+
else
|
157
|
+
raise InvalidFieldTypeError, type
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EditInPlace
|
4
|
+
# {Configuration} is a class that is capable of storing configuration for an edit_in_place
|
5
|
+
# {Builder}. Essentially all the options provided by edit_in_place reside in this class for
|
6
|
+
# easy reuse. This class is currently used in two locations---the global configuration in
|
7
|
+
# {EditInPlace.config} and the builder-specific configuration in {Builder#config}.
|
8
|
+
#
|
9
|
+
# @author Jacob Lockard
|
10
|
+
# @since 0.1.0
|
11
|
+
class Configuration
|
12
|
+
# The default mode in which fields should be rendered if left unpspecified in the
|
13
|
+
# configuration.
|
14
|
+
DEFAULT_MODE = :viewing
|
15
|
+
|
16
|
+
# The {FieldTypeRegistrar} used to store the list of registered field types.
|
17
|
+
# @return [FieldTypeRegistrar] the {FieldTypeRegistrar} that stores the list of registered
|
18
|
+
# field types.
|
19
|
+
attr_accessor :field_types
|
20
|
+
|
21
|
+
# The default {FieldOptions} instance to use when a builder renders a field. Note that this
|
22
|
+
# instance will be merged with the one passed directly to {Builder#field}.
|
23
|
+
# @return [FieldOptions] the default field options to use when rendering a field.
|
24
|
+
attr_accessor :field_options
|
25
|
+
|
26
|
+
# An array containing all the middleware classes permitted, in the order they should be run.
|
27
|
+
# @return [Array] the array of defined middlewares.
|
28
|
+
attr_accessor :defined_middlewares
|
29
|
+
|
30
|
+
# Creates a new, default instance of {Configuration}.
|
31
|
+
def initialize
|
32
|
+
@field_types = FieldTypeRegistrar.new
|
33
|
+
@field_options = FieldOptions.new(mode: DEFAULT_MODE)
|
34
|
+
@defined_middlewares = []
|
35
|
+
end
|
36
|
+
|
37
|
+
# Creates a deep copy of this {Configuration} that can be safely modified.
|
38
|
+
# @return [Configuration] a deep copy of this configuration.
|
39
|
+
def dup
|
40
|
+
c = Configuration.new
|
41
|
+
c.field_types = field_types.dup
|
42
|
+
c.field_options = field_options.dup
|
43
|
+
# Note that this is purposely NOT a deep copy---it doesn't make sense to duplicate classes.
|
44
|
+
c.defined_middlewares = defined_middlewares.dup
|
45
|
+
c
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EditInPlace
|
4
|
+
# A base class that allows one to easily and safely extend the {Builder} class with additional
|
5
|
+
# functionality. Every {ExtendedBuilder} contains a base builder to which it delegates all
|
6
|
+
# missing method calls. In this way, builder instances can be extended multiple times, like so:
|
7
|
+
# base = Builder.new({...})
|
8
|
+
# base.respond_to? :field # => true
|
9
|
+
#
|
10
|
+
# hello_builder = HelloBuilder.new(base_builder, {...}) # a sub-class of ExtendedBuilder
|
11
|
+
# hello_builder.respond_to? :hello # => true
|
12
|
+
# hello_builder.respond_to? :field # => true
|
13
|
+
#
|
14
|
+
# world_builder = WorldBuilder.new(hello_builder, {...}) # a sub-class of ExtendedBuilder
|
15
|
+
# world_builder.respond_to? :world # => true
|
16
|
+
# world_builder.respond_to? :hello # => true
|
17
|
+
# world_builder.respond_to? :field # => true
|
18
|
+
#
|
19
|
+
# A word of caution is in order, however! An {ExtendedBuilder} should *never*
|
20
|
+
# override a method on the base builder. While this may seem like a convenient way to add new
|
21
|
+
# methods to {Builder}, it can cause problems, since other methods further along in the chain
|
22
|
+
# will not call the overriden one. {ExtendedBuilder} is intended only for adding new methods,
|
23
|
+
# not mdifying existing ones.
|
24
|
+
#
|
25
|
+
# @author Jacob Lockard
|
26
|
+
# @since 0.1.0
|
27
|
+
class ExtendedBuilder
|
28
|
+
# @return [Object] the base builder instance which this {ExtendedBuilder} extends. It should
|
29
|
+
# quack like a {Builder}.
|
30
|
+
attr_reader :base
|
31
|
+
|
32
|
+
delegate_missing_to :base
|
33
|
+
|
34
|
+
# Creates a new {ExtendedBuilder} with the given base builder.
|
35
|
+
# @param base [Object] the base builder to extend. It should quack like a {Builder}.
|
36
|
+
def initialize(base)
|
37
|
+
@base = base
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EditInPlace
|
4
|
+
# {FieldOptions} is a class that stores the options and context required to render a field.
|
5
|
+
#
|
6
|
+
# @author Jacob Lockard
|
7
|
+
# @since 0.1.0
|
8
|
+
class FieldOptions
|
9
|
+
# @overload mode
|
10
|
+
# Gets the "mode" with which the field should be rendered. For example, a text field may
|
11
|
+
# render an +<input type="text" ...>+ element when in an "editing" mode, but render simple
|
12
|
+
# text in a "viewing" mode.
|
13
|
+
# @return [Symbol] the mode in which the field should be rendered.
|
14
|
+
# @overload mode=
|
15
|
+
# Sets the new mode.
|
16
|
+
# @param mode [String, Symbol, nil] the new mode.
|
17
|
+
# @note All strings will be converted to symbols.
|
18
|
+
# @return [void]
|
19
|
+
attr_reader :mode
|
20
|
+
|
21
|
+
# The view context to be used when rendering the field. This view context can be derived in
|
22
|
+
# a number of ways, most commonly by creating a new instance of +ActionView::Base+.
|
23
|
+
# @return the view context to be used when rendering the field.
|
24
|
+
attr_accessor :view
|
25
|
+
|
26
|
+
# An array of middleware instances that should be applied to the field's input.
|
27
|
+
# @return the array of middlewares.
|
28
|
+
attr_accessor :middlewares
|
29
|
+
|
30
|
+
# @overload initialize(options)
|
31
|
+
# Creates a new instance of {FieldOptions} with the given options.
|
32
|
+
# @param options [Hash, #[]] a hash containing the given field options.
|
33
|
+
# @option options [Symbol] :mode the {#mode} in which fields should be rendered.
|
34
|
+
# @option options :view the {#view} context to use when rendering the field.
|
35
|
+
# @overload initialize
|
36
|
+
# Creates a new instance of {FieldOptions} with the default options.
|
37
|
+
def initialize(options = {})
|
38
|
+
self.mode = options[:mode]
|
39
|
+
self.view = options[:view]
|
40
|
+
self.middlewares = options[:middlewares] || []
|
41
|
+
end
|
42
|
+
|
43
|
+
# Documentation for this method resides in the attribute declaration.
|
44
|
+
def mode=(mode)
|
45
|
+
@mode = mode.nil? ? nil : mode.to_sym
|
46
|
+
end
|
47
|
+
|
48
|
+
# Creates a deep copy of this {FieldOptions} instance that can be safely modified.
|
49
|
+
# @return [FieldOptions] a deep copy of this {FieldOptions} instance.
|
50
|
+
def dup
|
51
|
+
f = FieldOptions.new
|
52
|
+
f.mode = mode
|
53
|
+
f.view = view
|
54
|
+
f.middlewares = middlewares.deep_dup
|
55
|
+
f
|
56
|
+
end
|
57
|
+
|
58
|
+
# Merges the given {FieldOptions} instance into this one. Modes and view contexts from the
|
59
|
+
# other instance will overwrite those in this instance if present. The other instance is
|
60
|
+
# duplicated before being merged, so it can be safely modified after the fact. All
|
61
|
+
# middleware arrays will be merged.
|
62
|
+
# @param other [FieldOptions] the other field options to merge into this one.
|
63
|
+
# @return [void]
|
64
|
+
def merge!(other)
|
65
|
+
other = other.dup
|
66
|
+
|
67
|
+
self.mode = other.mode unless other.mode.nil?
|
68
|
+
self.view = other.view unless other.view.nil?
|
69
|
+
self.middlewares += other.middlewares
|
70
|
+
end
|
71
|
+
|
72
|
+
# Creates a _new_ {FieldOptions} instance that is the result of merging the given
|
73
|
+
# {FieldOptions} instance into this one. Neither instance is modified, and both are
|
74
|
+
# duplicated, meaning that they can be safely modified after the fact. Merging occurs exactly
|
75
|
+
# as with {#merge!}.
|
76
|
+
# @param other [FieldOptions] the other field options to merge into this one.
|
77
|
+
# @return [FieldOptions] the result of merging the two instances.
|
78
|
+
def merge(other)
|
79
|
+
new = dup
|
80
|
+
new.merge!(other)
|
81
|
+
new
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EditInPlace
|
4
|
+
# {FieldType} is a class that represents a single type of field. A field is a single,
|
5
|
+
# self-contained component that displays data in various "modes", typically either "viewing" or
|
6
|
+
# "editing". Field types provide the templates for similar fields.
|
7
|
+
#
|
8
|
+
# @abstract
|
9
|
+
# @author Jacob Lockard
|
10
|
+
# @since 0.1.0
|
11
|
+
class FieldType
|
12
|
+
# Render the field, given a {FieldOptions} instance and an array of arguments passed by
|
13
|
+
# the caller.
|
14
|
+
#
|
15
|
+
# While subclasses may override this method as appropriate, the default
|
16
|
+
# implementation simply does two things:
|
17
|
+
# 1. uses {#validate_mode!} to ensure that the mode is supported, and
|
18
|
+
# 2. calls a +render_*+ method.
|
19
|
+
# For example, if the mode were +:admin_editing+, then a +render_admin_editing+ method would
|
20
|
+
# be called. Naturally, the +render_*+ methods need to be defined by the subclass.
|
21
|
+
# @param options [FieldOptions] options passed by the {Builder} instance that should
|
22
|
+
# be used to render the field.
|
23
|
+
# @param args [Array<Object>] the arguments passed by the field creator.
|
24
|
+
# @return [String] the rendered HTML.
|
25
|
+
def render(options, *args)
|
26
|
+
validate_mode!(options.mode)
|
27
|
+
send("render_#{options.mode}", options, *args)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Gets the modes that are supported by this field type, +:viewing+ and +:editing+ by default.
|
31
|
+
# @return [Array<Symbol>] the modes supported by this field type.
|
32
|
+
# @note Subclasses should override this method as appropriate.
|
33
|
+
def supported_modes
|
34
|
+
%i[viewing editing]
|
35
|
+
end
|
36
|
+
|
37
|
+
# @!method dup
|
38
|
+
# Should create a deep copy of this {FieldType} that can be safely modified.
|
39
|
+
# @return [FieldType] a deep copy of this field type.
|
40
|
+
# @note This method should be overriden as necessary by subclasses to duplicate any added
|
41
|
+
# data.
|
42
|
+
|
43
|
+
protected
|
44
|
+
|
45
|
+
# Ensures that the given mode is supported by this field type.
|
46
|
+
# @param mode [Symbol] the mode to validate.
|
47
|
+
# @return [void]
|
48
|
+
def validate_mode!(mode)
|
49
|
+
raise UnsupportedModeError, mode unless supported_modes.include? mode
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EditInPlace
|
4
|
+
# {FieldTypeRegistrar} is a subcalss of {Registrar} that only allows {FieldType}
|
5
|
+
# instances to be registered.
|
6
|
+
#
|
7
|
+
# @author Jacob Lockard
|
8
|
+
# @since 0.1.0
|
9
|
+
class FieldTypeRegistrar < Registrar
|
10
|
+
protected
|
11
|
+
|
12
|
+
# Adds to the default `validate_registration!` implementation by ensuring that only
|
13
|
+
# {FieldType} instances can be registered.
|
14
|
+
# @param name [Symbol] the name to validate.
|
15
|
+
# @param field_type [FieldType] the field type to validate.
|
16
|
+
# @return [void]
|
17
|
+
def validate_registration!(name, field_type)
|
18
|
+
super
|
19
|
+
raise InvalidFieldTypeError, field_type unless field_type.is_a? FieldType
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EditInPlace
|
4
|
+
# An error that occurs when an object that cannot be parsed into a {FieldType} is used as one.
|
5
|
+
class InvalidFieldTypeError < Error
|
6
|
+
# Creates a new instance of {InvalidFieldTypeError} with the given field type that
|
7
|
+
# caused the error.
|
8
|
+
# @param field_type [object] the field type that caused the error; used in the error message.
|
9
|
+
def initialize(field_type)
|
10
|
+
super("'#{field_type.inspect}' is not a valid field type!")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EditInPlace
|
4
|
+
# {Registrar} is a class that is capable of storing a list of objects registered with symbol
|
5
|
+
# names. Note that it makes no attempt to validate the objects registered. If such validation
|
6
|
+
# is required feel free to subclass {Registrar} and override the {validate_registration!}
|
7
|
+
# method.
|
8
|
+
#
|
9
|
+
# @author Jacob Lockard
|
10
|
+
# @since 0.1.0
|
11
|
+
class Registrar
|
12
|
+
# Creates a new instance of {Registrar}.
|
13
|
+
def initialize
|
14
|
+
@registrations = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
# Creates a deep copy of this {Registrar} that can be safely modified.
|
18
|
+
# @return [Registrar] a deep copy of this registrar.
|
19
|
+
def dup
|
20
|
+
r = Registrar.new
|
21
|
+
r.register_all(all)
|
22
|
+
r
|
23
|
+
end
|
24
|
+
|
25
|
+
# Registers the given object with the given symbol name.
|
26
|
+
# @param name [Symbol] the symbol name with which to associate the object.
|
27
|
+
# @param object the object to register.
|
28
|
+
# @return [void]
|
29
|
+
# @see #register_all
|
30
|
+
def register(name, object)
|
31
|
+
validate_registration!(name, object)
|
32
|
+
registrations[name] = object
|
33
|
+
end
|
34
|
+
|
35
|
+
# Registers all the symbol names and objects in the given hash. All elements of the hash are
|
36
|
+
# passed through {#register}.
|
37
|
+
# @param objects [Hash<(Symbol, Object)>] the hash of names and objects to register.
|
38
|
+
# @return [void]
|
39
|
+
# @see #register
|
40
|
+
def register_all(objects)
|
41
|
+
# The identical loops are necessary to prevent anyything from being registered if even one
|
42
|
+
# fails the validation.
|
43
|
+
|
44
|
+
# rubocop:disable Style/CombinableLoops
|
45
|
+
objects.each { |n, t| validate_registration!(n, t) }
|
46
|
+
objects.each { |n, t| register(n, t) }
|
47
|
+
# rubocop:enable Style/CombinableLoops
|
48
|
+
end
|
49
|
+
|
50
|
+
# Attempts to find an object associated with the given symbol name.
|
51
|
+
# @param name [Symbol] the symbol name to search for.
|
52
|
+
# @return [Object] if found.
|
53
|
+
# @return [nil] if no object was associated with the given name.
|
54
|
+
def find(name)
|
55
|
+
registrations[name]
|
56
|
+
end
|
57
|
+
|
58
|
+
# Gets a hash of all registrations. Note that this hash is a deep clone of the actual,
|
59
|
+
# internal one and can be safely modified.
|
60
|
+
# @return [Hash<(Symbol, Object)>] the hash of registered names and objects.
|
61
|
+
def all
|
62
|
+
registrations.deep_dup
|
63
|
+
end
|
64
|
+
|
65
|
+
protected
|
66
|
+
|
67
|
+
# @return [Hash<(Symbol, Object)>] A hash of registrations.
|
68
|
+
attr_reader :registrations
|
69
|
+
|
70
|
+
# Should ensure that the given registration is valid. By default a registration is valid if
|
71
|
+
# its name is not already taken and is a symbol. Subclasses may override this method to add
|
72
|
+
# validation rules. Errors should be raised for any invalid registrations.
|
73
|
+
# @param name [Symbol] the name to validate.
|
74
|
+
# @param _object [Object] the object to validate.
|
75
|
+
# @return [void]
|
76
|
+
def validate_registration!(name, _object)
|
77
|
+
if registrations.key?(name)
|
78
|
+
raise DuplicateRegistrationError, <<~ERR
|
79
|
+
The name '#{name}' has already been registered!
|
80
|
+
ERR
|
81
|
+
end
|
82
|
+
|
83
|
+
unless name.is_a?(Symbol)
|
84
|
+
raise InvalidRegistrationNameError, <<~ERR
|
85
|
+
The name '#{name}' is not a valid symbol registration name!
|
86
|
+
ERR
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EditInPlace
|
4
|
+
# An error that occurs when no registered field types exist for the field type name given.
|
5
|
+
class UnregisteredFieldTypeError < Error
|
6
|
+
# Creates a new instance of {UnregisteredFieldTypeError} with the given field type name that
|
7
|
+
# caused the error.
|
8
|
+
# @param name [Symbol] the field type that caused the error; used in the error message.
|
9
|
+
def initialize(name)
|
10
|
+
super("No field types are registered with the name '#{name}'")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EditInPlace
|
4
|
+
# An error that occurs when a field is rendered with a mode that is does not support.
|
5
|
+
class UnsupportedModeError < Error
|
6
|
+
# Creates a new instance of {UnsupportedModeError} with the given mode that
|
7
|
+
# caused the error.
|
8
|
+
# @param mode [Symbol] the mode that caused the error.
|
9
|
+
def initialize(mode)
|
10
|
+
super("The mode '#{mode}' is not supported by this field type!")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
metadata
ADDED
@@ -0,0 +1,170 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: edit_in_place
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jacob
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-07-15 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: byebug
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '11.0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '11.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec-rails
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '5.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '5.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rubocop
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.18'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.18'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rubocop-rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.4'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '2.4'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: simplecov
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.17'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.17'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: middlegem
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 0.1.0
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 0.1.0
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rails
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 6.1.3
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: 6.1.3.2
|
107
|
+
type: :runtime
|
108
|
+
prerelease: false
|
109
|
+
version_requirements: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - "~>"
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: 6.1.3
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: 6.1.3.2
|
117
|
+
description: 'edit_in_place is a Ruby on Rails plugin that allows the creation of
|
118
|
+
user interfaces that allow the user to edit content in an intuitive, natural, "in
|
119
|
+
place" way.
|
120
|
+
|
121
|
+
'
|
122
|
+
email:
|
123
|
+
- jacoblockard99@gmail.com
|
124
|
+
executables: []
|
125
|
+
extensions: []
|
126
|
+
extra_rdoc_files: []
|
127
|
+
files:
|
128
|
+
- MIT-LICENSE
|
129
|
+
- README.md
|
130
|
+
- Rakefile
|
131
|
+
- lib/edit_in_place.rb
|
132
|
+
- lib/edit_in_place/builder.rb
|
133
|
+
- lib/edit_in_place/configuration.rb
|
134
|
+
- lib/edit_in_place/duplicate_registration_error.rb
|
135
|
+
- lib/edit_in_place/extended_builder.rb
|
136
|
+
- lib/edit_in_place/field_options.rb
|
137
|
+
- lib/edit_in_place/field_type.rb
|
138
|
+
- lib/edit_in_place/field_type_registrar.rb
|
139
|
+
- lib/edit_in_place/invalid_field_type_error.rb
|
140
|
+
- lib/edit_in_place/invalid_registration_name_error.rb
|
141
|
+
- lib/edit_in_place/railtie.rb
|
142
|
+
- lib/edit_in_place/registrar.rb
|
143
|
+
- lib/edit_in_place/unregistered_field_type_error.rb
|
144
|
+
- lib/edit_in_place/unsupported_mode_error.rb
|
145
|
+
- lib/edit_in_place/version.rb
|
146
|
+
- lib/tasks/edit_in_place_tasks.rake
|
147
|
+
homepage: https://github.com/jacoblockard99/edit_in_place
|
148
|
+
licenses:
|
149
|
+
- MIT
|
150
|
+
metadata: {}
|
151
|
+
post_install_message:
|
152
|
+
rdoc_options: []
|
153
|
+
require_paths:
|
154
|
+
- lib
|
155
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: 2.5.0
|
160
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
161
|
+
requirements:
|
162
|
+
- - ">="
|
163
|
+
- !ruby/object:Gem::Version
|
164
|
+
version: '0'
|
165
|
+
requirements: []
|
166
|
+
rubygems_version: 3.2.3
|
167
|
+
signing_key:
|
168
|
+
specification_version: 4
|
169
|
+
summary: A Rails plugin that facilitates the creation of intuitive editable content.
|
170
|
+
test_files: []
|