active_preview 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +43 -0
- data/Rakefile +33 -0
- data/lib/active_preview/association_helper.rb +17 -0
- data/lib/active_preview/override_persistence.rb +28 -0
- data/lib/active_preview/preview.rb +34 -0
- data/lib/active_preview/previewing.rb +66 -0
- data/lib/active_preview/version.rb +3 -0
- data/lib/active_preview.rb +7 -0
- data/lib/rails/generators/active_preview/preview_generator.rb +16 -0
- data/lib/tasks/active_preview_tasks.rake +4 -0
- metadata +167 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4271152b50d079c85187b6f598c0be48fac5aab3e3a4d7cfe3a1e76b380f2e6f
|
4
|
+
data.tar.gz: 70513536578312ae01f25b4ad61c9f36ef1697d3cc6fa415739c970e8fc9df92
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 62b5504560d84ce3c20dca30cd139aec9b26a874ed085a2bef93f17da13acebea60bd2cc02f1bdf0d8b5f2601246cb607e7b99d0032e7afc49ab1c4ccf2296b3
|
7
|
+
data.tar.gz: 18a667682ecb1eabad0b54dd650c222385dab411acabe658600fd80463552e09d216f4f4db42762838f857609bf9929c0ad0c7cc9acad0274d8225f175a09475
|
data/README.md
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# ActivePreview
|
2
|
+
active_preview is a Ruby on Rails plugin that makes it easy to create previews of updates to records. The previews don't interact with the database but otherwise behave like ActiveRecord objects.
|
3
|
+
|
4
|
+
## Usage
|
5
|
+
To preview a model, first run the generator:
|
6
|
+
```bash
|
7
|
+
$ rails generate active_preview:preview --model MyModel
|
8
|
+
```
|
9
|
+
This will create `my_model_preview.rb` in `app/previews`. If you have methods that touch the database in your model that need to be used with previewed data, override them there.
|
10
|
+
|
11
|
+
Next, add the `#preview` method to your model:
|
12
|
+
```ruby
|
13
|
+
class MyModel < ActiveRecord::Base
|
14
|
+
include ActivePreview::Previewing
|
15
|
+
end
|
16
|
+
```
|
17
|
+
|
18
|
+
Now you can pass an attribute hash or `ActionController::Parameters` object to `my_model.preview` to get a preview object with updated attributes that behaves exactly like your model.
|
19
|
+
|
20
|
+
For models with associations, run the generator for each associated model and add `include ActivePreview::Previewing` to each model class.
|
21
|
+
|
22
|
+
## Installation
|
23
|
+
Add this line to your application's Gemfile:
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
gem 'active_preview'
|
27
|
+
```
|
28
|
+
|
29
|
+
And then execute:
|
30
|
+
```bash
|
31
|
+
$ bundle
|
32
|
+
```
|
33
|
+
|
34
|
+
Or install it yourself as:
|
35
|
+
```bash
|
36
|
+
$ gem install active_preview
|
37
|
+
```
|
38
|
+
|
39
|
+
## Contributing
|
40
|
+
Contribution directions go here.
|
41
|
+
|
42
|
+
## License
|
43
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'ActivePreview'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.md')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
|
19
|
+
|
20
|
+
|
21
|
+
|
22
|
+
require 'bundler/gem_tasks'
|
23
|
+
|
24
|
+
require 'rake/testtask'
|
25
|
+
|
26
|
+
Rake::TestTask.new(:test) do |t|
|
27
|
+
t.libs << 'test'
|
28
|
+
t.pattern = 'test/**/*_test.rb'
|
29
|
+
t.verbose = false
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
task default: :test
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module AssociationHelper
|
2
|
+
def association_from_key(key)
|
3
|
+
key.to_s.split("_").tap { |arr| arr.delete("attributes") }.join("_")
|
4
|
+
end
|
5
|
+
|
6
|
+
def associations(klass)
|
7
|
+
klass.reflect_on_all_associations.map { |a| a.name.to_s }
|
8
|
+
end
|
9
|
+
|
10
|
+
def association_class(klass:, key:)
|
11
|
+
klass.reflect_on_association(association_from_key(key)).klass
|
12
|
+
end
|
13
|
+
|
14
|
+
def singular?(name)
|
15
|
+
name.singularize == name
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module OverridePersistence
|
2
|
+
# TODO: maybe raise a helpful error?
|
3
|
+
def becomes; end
|
4
|
+
def becomes! ; end
|
5
|
+
def decrement; end
|
6
|
+
def decrement!; end
|
7
|
+
def delete; end
|
8
|
+
def destroy; end
|
9
|
+
def destroy!; end
|
10
|
+
def destroyed? ; end
|
11
|
+
def increment; end
|
12
|
+
def increment! ; end
|
13
|
+
def new_record?; end
|
14
|
+
def persisted?; end
|
15
|
+
def reload; end
|
16
|
+
def save; end
|
17
|
+
def save! ; end
|
18
|
+
def toggle; end
|
19
|
+
def toggle!; end
|
20
|
+
def touch ; end
|
21
|
+
def update; end
|
22
|
+
def update!; end
|
23
|
+
def update_attribute; end
|
24
|
+
def update_attributes; end
|
25
|
+
def update_attributes!; end
|
26
|
+
def update_column; end
|
27
|
+
def update_columns ; end
|
28
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require "active_preview/override_persistence"
|
2
|
+
|
3
|
+
module ActivePreview
|
4
|
+
class Preview < SimpleDelegator
|
5
|
+
include OverridePersistence
|
6
|
+
include AssociationHelper
|
7
|
+
|
8
|
+
def initialize(record)
|
9
|
+
redefine_associations(record)
|
10
|
+
super(record)
|
11
|
+
end
|
12
|
+
|
13
|
+
def original
|
14
|
+
__getobj__
|
15
|
+
end
|
16
|
+
|
17
|
+
# ~ used in place of association names
|
18
|
+
SINGULAR = %w(create_~ create_~! reload_~)
|
19
|
+
COLLECTION = %w(~_id ~_id= ~<<)
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def redefine_associations(record)
|
24
|
+
associations(record.class).each do |a|
|
25
|
+
next if respond_to? a # avoid redefining methods
|
26
|
+
methods = singular?(a) ? SINGULAR : COLLECTION
|
27
|
+
methods.each do |method|
|
28
|
+
self.class.send(:define_method, method.gsub("~", a)) {}
|
29
|
+
end
|
30
|
+
self.class.send(:attr_accessor, a)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module ActivePreview
|
2
|
+
module Previewing
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
include AssociationHelper
|
5
|
+
|
6
|
+
included do
|
7
|
+
def self.collection_preview(saved, params)
|
8
|
+
child_args(saved, params).map do |obj, attrs|
|
9
|
+
if obj
|
10
|
+
obj.preview(attrs)
|
11
|
+
else
|
12
|
+
self.new(attrs).preview
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def self.child_args(saved, child_attrs)
|
20
|
+
if saved.size > child_attrs.size
|
21
|
+
saved.zip(child_attrs)
|
22
|
+
else
|
23
|
+
child_attrs.zip(saved).map(&:reverse!)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns an unsaved object with updated attributes, along with any
|
29
|
+
# child objects and their updated attributes.
|
30
|
+
# Note that the associations are not assigned, so parent.children will be
|
31
|
+
# empty
|
32
|
+
def preview(params)
|
33
|
+
merged_object(params).tap do |p|
|
34
|
+
child_keys(params).each do |params_key|
|
35
|
+
association = association_from_key(params_key)
|
36
|
+
next unless associations(self.class).include?(association)
|
37
|
+
p.send("#{association}=", generate_children(params, params_key))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def merged_object(new_attrs)
|
45
|
+
preview_class.new(self.class.new(self.attributes.merge(new_attrs)))
|
46
|
+
end
|
47
|
+
|
48
|
+
def preview_class
|
49
|
+
"#{self.class}Preview".constantize
|
50
|
+
end
|
51
|
+
|
52
|
+
def child_keys(hash)
|
53
|
+
hash.map do |k, v|
|
54
|
+
k if v.is_a?(ActionController::Parameters) || v.is_a?(Hash)
|
55
|
+
end.compact
|
56
|
+
end
|
57
|
+
|
58
|
+
def generate_children(params, *params_keys)
|
59
|
+
child_params = params.dig(*params_keys).to_h.values
|
60
|
+
.sort! { |a, b| a["id"] <=> b["id"] }
|
61
|
+
klass = association_class(klass: self.class, key: params_keys.last)
|
62
|
+
saved = klass.where(id: child_params.map { |p| p["id"] }).order(:id)
|
63
|
+
klass.collection_preview(saved, child_params)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module ActivePreview
|
2
|
+
module Generators
|
3
|
+
class PreviewGenerator < Rails::Generators::Base
|
4
|
+
desc "Creates a Preview class for the given model and its associated "\
|
5
|
+
"models"
|
6
|
+
class_option :model, type: :string
|
7
|
+
def create_preview
|
8
|
+
model = options[:model].downcase
|
9
|
+
empty_directory "app/previews"
|
10
|
+
create_file "app/previews/#{model}_preview.rb",
|
11
|
+
"class #{model.capitalize}Preview < ActivePreview::Preview"\
|
12
|
+
"\nend\n"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
metadata
ADDED
@@ -0,0 +1,167 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: active_preview
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Syd Young
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-11-05 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '5.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec-rails
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: database_cleaner
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.7'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.7'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: sqlite3
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '1.3'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1.3'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rubocop
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0.60'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0.60'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rubocop-github
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: byebug
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
description:
|
126
|
+
email:
|
127
|
+
- sky@eff.org
|
128
|
+
executables: []
|
129
|
+
extensions: []
|
130
|
+
extra_rdoc_files: []
|
131
|
+
files:
|
132
|
+
- README.md
|
133
|
+
- Rakefile
|
134
|
+
- lib/active_preview.rb
|
135
|
+
- lib/active_preview/association_helper.rb
|
136
|
+
- lib/active_preview/override_persistence.rb
|
137
|
+
- lib/active_preview/preview.rb
|
138
|
+
- lib/active_preview/previewing.rb
|
139
|
+
- lib/active_preview/version.rb
|
140
|
+
- lib/rails/generators/active_preview/preview_generator.rb
|
141
|
+
- lib/tasks/active_preview_tasks.rake
|
142
|
+
homepage: https://github.com/efforg/active_preview
|
143
|
+
licenses:
|
144
|
+
- MIT
|
145
|
+
metadata: {}
|
146
|
+
post_install_message:
|
147
|
+
rdoc_options: []
|
148
|
+
require_paths:
|
149
|
+
- lib
|
150
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
151
|
+
requirements:
|
152
|
+
- - ">="
|
153
|
+
- !ruby/object:Gem::Version
|
154
|
+
version: '0'
|
155
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
requirements: []
|
161
|
+
rubyforge_project:
|
162
|
+
rubygems_version: 2.7.6
|
163
|
+
signing_key:
|
164
|
+
specification_version: 4
|
165
|
+
summary: Create previews of ActiveRecord objects that don't modify
|
166
|
+
the database
|
167
|
+
test_files: []
|