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 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,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EditInPlace
4
+ # An error that occurs when a name that has already been registered is registered.
5
+ class DuplicateRegistrationError < Error; end
6
+ 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,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EditInPlace
4
+ # An error that occurs when an object is registered with an invalid name. This error usually
5
+ # occurs when the name is not a symbol.
6
+ class InvalidRegistrationNameError < Error; end
7
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EditInPlace
4
+ class Railtie < ::Rails::Railtie
5
+ end
6
+ 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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EditInPlace
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+ # desc "Explaining what the task does"
3
+ # task :edit_in_place do
4
+ # # Task goes here
5
+ # 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: []