hyper-mesh 0.4.0 → 0.5.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 +4 -4
- data/README.md +59 -204
- data/config/routes.rb +1 -1
- data/docs/action_cable_quickstart.md +13 -36
- data/docs/configuration_details.md +62 -0
- data/docs/pusher_faker_quickstart.md +28 -0
- data/docs/pusher_quickstart.md +17 -0
- data/docs/simple_poller_quickstart.md +5 -0
- data/examples/words/Gemfile +4 -3
- data/examples/words/Gemfile.lock +33 -32
- data/examples/words/app/views/components.rb +4 -2
- data/examples/words/config/initializers/hyper_mesh.rb +14 -0
- data/examples/words/config/routes.rb +1 -1
- data/hyper-mesh-0.4.0.gem +0 -0
- data/lib/hyper-mesh.rb +2 -1
- data/lib/hypermesh/version.rb +1 -1
- data/lib/reactive_record/active_record/public_columns_hash.rb +2 -2
- data/lib/reactive_record/active_record/reactive_record/isomorphic_base.rb +1 -1
- data/lib/reactive_record/engine.rb +2 -4
- data/lib/reactive_record/permissions.rb +30 -7
- data/lib/synchromesh/client_drivers.rb +3 -5
- data/lib/synchromesh/connection.rb +34 -8
- data/lib/synchromesh/synchromesh.rb +11 -7
- data/lib/synchromesh/synchromesh_controller.rb +9 -1
- data/path_release_steps.md +9 -0
- data/reactive_record_test_app/Gemfile +2 -1
- data/reactive_record_test_app/Gemfile.lock +6 -6
- data/reactive_record_test_app/config/application.rb +0 -1
- data/reactive_record_test_app/config/initializers/hyper_mesh_legacy_behavior.rb +5 -0
- data/reactive_record_test_app/config/routes.rb +3 -3
- data/spec/reactive_record/edge_cases_spec.rb +1 -1
- data/spec/reactive_record/many_to_many_spec.rb +1 -1
- data/spec/reactive_record/revert_spec.rb +1 -0
- data/spec/reactive_record/update_associations_spec.rb +1 -0
- data/spec/reactive_record/update_scopes_spec.rb +1 -0
- data/spec/synchromesh/crud_access_regulation/broadcast_controls_access_spec.rb +0 -6
- data/spec/synchromesh/crud_access_regulation/model_policies_spec.rb +8 -6
- data/spec/synchromesh/integration/has_many_through_spec.rb +0 -4
- data/spec/test_app/config/routes.rb +1 -1
- data/work-in-progress-drinking.png +0 -0
- metadata +8 -4
- data/examples/words/config/initializers/synchromesh.rb +0 -5
- data/lib/synchromesh/reactive_record/permission_patches.rb +0 -43
@@ -0,0 +1,62 @@
|
|
1
|
+
## Installation
|
2
|
+
|
3
|
+
If you do not already have hyper-react installed, then use the reactrb-rails-generator gem to setup hyper-react, reactive-record and associated gems.
|
4
|
+
|
5
|
+
Then add this line to your application's Gemfile:
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
gem 'HyperMesh'
|
9
|
+
```
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle install
|
14
|
+
|
15
|
+
Also you must `require 'hyper-tracemesh'` from your client side code. The easiest way is to
|
16
|
+
find the `require 'reactive-record'` line (typically in `components.rb`) and replace it with
|
17
|
+
`require 'HyperMesh'`.
|
18
|
+
|
19
|
+
## Configuration
|
20
|
+
|
21
|
+
Add an initializer like this:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
# for rails this would go in: config/initializers/HyperMesh.rb
|
25
|
+
HyperMesh.configuration do |config|
|
26
|
+
config.transport = :simple_poller # or :none, action_cable, :pusher - see below)
|
27
|
+
end
|
28
|
+
# for a minimal setup you will need to define at least one channel, which you can do
|
29
|
+
# in the same file as your initializer.
|
30
|
+
# Normally you would put these policies in the app/policies/ directory
|
31
|
+
class ApplicationPolicy
|
32
|
+
# allow all clients to connect to the Application channel
|
33
|
+
regulate_connection { true } # or always_allow_connection for short
|
34
|
+
# broadcast all model changes over the Application channel *DANGEROUS*
|
35
|
+
regulate_all_broadcasts { |policy| policy.send_all }
|
36
|
+
end
|
37
|
+
```
|
38
|
+
|
39
|
+
Assuming you are up and running with Hyper-React on Rails:
|
40
|
+
|
41
|
+
1. **Add the gem**
|
42
|
+
add `gem 'hyper-mesh'`, and bundle install
|
43
|
+
6. **Add the models directory to asset path**
|
44
|
+
```ruby
|
45
|
+
# application.rb
|
46
|
+
config.assets.paths << ::Rails.root.join('app', 'models').to_s
|
47
|
+
```
|
48
|
+
|
49
|
+
2. **Require HyperMesh instead of HyperReact**
|
50
|
+
replace `require 'hyper-react'` with `require 'hyper-mesh'` in the components manifest (`app/views/components.rb`.)
|
51
|
+
3. **Require your models on the client_side_scoping**
|
52
|
+
add `require 'models'` to the bottom of the components manifest
|
53
|
+
4. add a models manifest in the models directory:
|
54
|
+
```ruby
|
55
|
+
# app/models/models.rb
|
56
|
+
require_tree './public'
|
57
|
+
```
|
58
|
+
5. create a `public` directory in your models directory and move any models that you want access to on the client into this directory. Access to these models will be protected by *Policies* you will be creating later.
|
59
|
+
|
60
|
+
A minimal HyperMesh configuration consists of a simple initializer file, and at least one *Policy* class that will *authorize* who gets to see what.
|
61
|
+
|
62
|
+
The initializer file specifies what transport will be used. Currently you can use [Pusher](http://pusher.com), ActionCable (if using Rails 5), Pusher-Fake (for development) or a Simple Poller for testing etc.
|
@@ -0,0 +1,28 @@
|
|
1
|
+
### Pusher-Fake
|
2
|
+
|
3
|
+
You can also use the [Pusher-Fake](https://github.com/tristandunn/pusher-fake) gem while in development. Setup is a little tricky. First
|
4
|
+
add `gem 'pusher-fake'` to the development and/or test section of your gem file. Then setup your config file:
|
5
|
+
|
6
|
+
```ruby
|
7
|
+
# typically config/initializers/HyperMesh.rb
|
8
|
+
# or you can do a similar setup in your tests (see this gem's specs)
|
9
|
+
require 'pusher'
|
10
|
+
require 'pusher-fake'
|
11
|
+
# The app_id, key, and secret need to be assigned directly to Pusher
|
12
|
+
# so PusherFake will work.
|
13
|
+
Pusher.app_id = "MY_TEST_ID" # you use the real or fake values
|
14
|
+
Pusher.key = "MY_TEST_KEY"
|
15
|
+
Pusher.secret = "MY_TEST_SECRET"
|
16
|
+
# The next line actually starts the pusher-fake server (see the Pusher-Fake readme for details.)
|
17
|
+
require 'pusher-fake/support/base' # if using pusher with rspec change this to pusher-fake/support/rspec
|
18
|
+
# now copy over the credentials, and merge with PusherFake's config details
|
19
|
+
HyperMesh.configuration do |config|
|
20
|
+
config.transport = :pusher
|
21
|
+
config.channel_prefix = "HyperMesh"
|
22
|
+
config.opts = {
|
23
|
+
app_id: Pusher.app_id,
|
24
|
+
key: Pusher.key,
|
25
|
+
secret: Pusher.secret
|
26
|
+
}.merge(PusherFake.configuration.web_options)
|
27
|
+
end
|
28
|
+
```
|
data/docs/pusher_quickstart.md
CHANGED
@@ -0,0 +1,17 @@
|
|
1
|
+
### Pusher Configuration
|
2
|
+
|
3
|
+
Add `gem 'pusher'` to your gem file, and add `//= require 'HyperMesh/pusher'` to your application.js file.
|
4
|
+
|
5
|
+
```ruby
|
6
|
+
# typically config/initializers/HyperMesh.rb
|
7
|
+
HyperMesh.configuration do |config|
|
8
|
+
config.transport = :pusher
|
9
|
+
config.opts = {
|
10
|
+
app_id: '2xxxx2',
|
11
|
+
key: 'dxxxxxxxxxxxxxxxxxx9',
|
12
|
+
secret: '2xxxxxxxxxxxxxxxxxx2',
|
13
|
+
encrypted: false # optional defaults to true
|
14
|
+
}
|
15
|
+
config.channel_prefix = 'syncromesh' # or any other string you want
|
16
|
+
end
|
17
|
+
```
|
@@ -29,6 +29,11 @@ Once you have hyper-react installed then add this initializer:
|
|
29
29
|
#config/initializers/synchromesh.rb
|
30
30
|
HyperMesh.configuration do |config|
|
31
31
|
config.transport = :simple_poller
|
32
|
+
# options
|
33
|
+
config.opts = {
|
34
|
+
seconds_between_poll: 5, # default is 0.5 you may need to increase if testing with Selenium
|
35
|
+
seconds_polled_data_will_be_retained: 1.hour # clears channel data after this time, default is 5 minutes
|
36
|
+
}
|
32
37
|
end
|
33
38
|
```
|
34
39
|
|
data/examples/words/Gemfile
CHANGED
@@ -43,16 +43,17 @@ group :development do
|
|
43
43
|
# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
|
44
44
|
gem 'spring'
|
45
45
|
gem 'spring-watcher-listen', '~> 2.0.0'
|
46
|
+
gem 'pry'
|
46
47
|
end
|
47
48
|
|
48
49
|
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
|
49
50
|
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
|
50
51
|
|
51
52
|
gem 'hyper-react'
|
53
|
+
gem 'opal-browser'
|
52
54
|
gem 'react-rails', '>= 1.3.0'
|
53
55
|
gem 'opal-rails', '>= 0.8.1'
|
54
56
|
gem 'therubyracer', platforms: :ruby
|
55
57
|
gem 'react-router-rails', '~> 0.13.3'
|
56
|
-
gem '
|
57
|
-
gem '
|
58
|
-
gem 'synchromesh', path: '../..'
|
58
|
+
gem 'hyper-router'
|
59
|
+
gem 'hyper-mesh', path: '../..'
|
data/examples/words/Gemfile.lock
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
PATH
|
2
2
|
remote: ../..
|
3
3
|
specs:
|
4
|
-
|
4
|
+
hyper-mesh (0.4.1)
|
5
5
|
activerecord (>= 0.3.0)
|
6
|
-
|
6
|
+
hyper-react (>= 0.10.0)
|
7
7
|
|
8
8
|
GEM
|
9
9
|
remote: https://rubygems.org/
|
@@ -45,13 +45,14 @@ GEM
|
|
45
45
|
i18n (~> 0.7)
|
46
46
|
minitest (~> 5.1)
|
47
47
|
tzinfo (~> 1.1)
|
48
|
-
arel (7.1.
|
48
|
+
arel (7.1.4)
|
49
49
|
babel-source (5.8.35)
|
50
50
|
babel-transpiler (0.7.0)
|
51
51
|
babel-source (>= 4.0, < 6)
|
52
52
|
execjs (~> 2.0)
|
53
53
|
builder (3.2.2)
|
54
|
-
byebug (9.0.
|
54
|
+
byebug (9.0.6)
|
55
|
+
coderay (1.1.1)
|
55
56
|
coffee-rails (4.2.1)
|
56
57
|
coffee-script (>= 2.2.0)
|
57
58
|
railties (>= 4.0.0, < 5.2.x)
|
@@ -68,6 +69,13 @@ GEM
|
|
68
69
|
globalid (0.3.7)
|
69
70
|
activesupport (>= 4.1.0)
|
70
71
|
hike (1.2.3)
|
72
|
+
hyper-react (0.10.0)
|
73
|
+
opal (>= 0.8.0)
|
74
|
+
opal-activesupport (>= 0.2.0)
|
75
|
+
react-rails
|
76
|
+
hyper-router (2.4.0)
|
77
|
+
hyper-react
|
78
|
+
opal-browser
|
71
79
|
i18n (0.7.0)
|
72
80
|
jbuilder (2.6.0)
|
73
81
|
activesupport (>= 3.0.0, < 5.1)
|
@@ -89,13 +97,12 @@ GEM
|
|
89
97
|
mime-types-data (~> 3.2015)
|
90
98
|
mime-types-data (3.2016.0521)
|
91
99
|
mini_portile2 (2.1.0)
|
92
|
-
minitest (5.9.
|
100
|
+
minitest (5.9.1)
|
93
101
|
multi_json (1.12.1)
|
94
102
|
nio4r (1.2.1)
|
95
|
-
nokogiri (1.6.8)
|
103
|
+
nokogiri (1.6.8.1)
|
96
104
|
mini_portile2 (~> 2.1.0)
|
97
|
-
|
98
|
-
opal (0.10.2)
|
105
|
+
opal (0.10.3)
|
99
106
|
hike (~> 1.2)
|
100
107
|
sourcemap (~> 0.1.0)
|
101
108
|
sprockets (~> 3.1)
|
@@ -115,7 +122,10 @@ GEM
|
|
115
122
|
rails (>= 4.0, < 6.0)
|
116
123
|
sprockets-rails (< 3.0)
|
117
124
|
paggio (0.2.6)
|
118
|
-
|
125
|
+
pry (0.10.4)
|
126
|
+
coderay (~> 1.1.0)
|
127
|
+
method_source (~> 0.8.1)
|
128
|
+
slop (~> 3.4)
|
119
129
|
puma (3.6.0)
|
120
130
|
rack (2.0.1)
|
121
131
|
rack-test (0.6.3)
|
@@ -144,7 +154,7 @@ GEM
|
|
144
154
|
rake (>= 0.8.7)
|
145
155
|
thor (>= 0.18.1, < 2.0)
|
146
156
|
rake (11.3.0)
|
147
|
-
rb-fsevent (0.9.
|
157
|
+
rb-fsevent (0.9.8)
|
148
158
|
rb-inotify (0.9.7)
|
149
159
|
ffi (>= 0.5.0)
|
150
160
|
react-rails (1.4.2)
|
@@ -157,20 +167,8 @@ GEM
|
|
157
167
|
react-router-rails (0.13.3.2)
|
158
168
|
rails (>= 3.1)
|
159
169
|
react-rails (~> 1.4.0)
|
160
|
-
reactive-record (0.8.3)
|
161
|
-
opal-browser
|
162
|
-
opal-rails
|
163
|
-
rails (>= 3.2.13)
|
164
|
-
react-rails
|
165
|
-
hyper-react
|
166
|
-
hyper-react (0.8.8)
|
167
|
-
opal (>= 0.8.0)
|
168
|
-
opal-activesupport (>= 0.2.0)
|
169
|
-
opal-browser (= 0.2.0)
|
170
170
|
reactrb-rails-generator (0.2.0)
|
171
171
|
rails (>= 4.0.0)
|
172
|
-
reactrb-router (0.8.2)
|
173
|
-
hyper-react
|
174
172
|
ref (2.0.0)
|
175
173
|
sass (3.4.22)
|
176
174
|
sass-rails (5.0.6)
|
@@ -179,11 +177,13 @@ GEM
|
|
179
177
|
sprockets (>= 2.8, < 4.0)
|
180
178
|
sprockets-rails (>= 2.0, < 4.0)
|
181
179
|
tilt (>= 1.1, < 3)
|
180
|
+
slop (3.6.0)
|
182
181
|
sourcemap (0.1.1)
|
183
|
-
spring (
|
184
|
-
|
182
|
+
spring (2.0.0)
|
183
|
+
activesupport (>= 4.2)
|
184
|
+
spring-watcher-listen (2.0.1)
|
185
185
|
listen (>= 2.7, < 4.0)
|
186
|
-
spring (
|
186
|
+
spring (>= 1.2, < 3.0)
|
187
187
|
sprockets (3.7.0)
|
188
188
|
concurrent-ruby (~> 1.0)
|
189
189
|
rack (> 1, < 3)
|
@@ -191,7 +191,7 @@ GEM
|
|
191
191
|
actionpack (>= 3.0)
|
192
192
|
activesupport (>= 3.0)
|
193
193
|
sprockets (>= 2.8, < 4.0)
|
194
|
-
sqlite3 (1.3.
|
194
|
+
sqlite3 (1.3.12)
|
195
195
|
therubyracer (0.12.2)
|
196
196
|
libv8 (~> 3.16.14.0)
|
197
197
|
ref
|
@@ -203,9 +203,9 @@ GEM
|
|
203
203
|
turbolinks-source (5.0.0)
|
204
204
|
tzinfo (1.2.2)
|
205
205
|
thread_safe (~> 0.1)
|
206
|
-
uglifier (3.0.
|
206
|
+
uglifier (3.0.3)
|
207
207
|
execjs (>= 0.3.0, < 3)
|
208
|
-
web-console (3.
|
208
|
+
web-console (3.4.0)
|
209
209
|
actionview (>= 5.0)
|
210
210
|
activemodel (>= 5.0)
|
211
211
|
debug_inspector
|
@@ -220,23 +220,24 @@ PLATFORMS
|
|
220
220
|
DEPENDENCIES
|
221
221
|
byebug
|
222
222
|
coffee-rails (~> 4.2)
|
223
|
+
hyper-mesh!
|
224
|
+
hyper-react
|
225
|
+
hyper-router
|
223
226
|
jbuilder (~> 2.5)
|
224
227
|
jquery-rails
|
225
228
|
listen (~> 3.0.5)
|
229
|
+
opal-browser
|
226
230
|
opal-rails (>= 0.8.1)
|
231
|
+
pry
|
227
232
|
puma (~> 3.0)
|
228
233
|
rails (~> 5.0.0, >= 5.0.0.1)
|
229
234
|
react-rails (>= 1.3.0)
|
230
235
|
react-router-rails (~> 0.13.3)
|
231
|
-
reactive-record (>= 0.8.0)
|
232
|
-
hyper-react
|
233
236
|
reactrb-rails-generator
|
234
|
-
reactrb-router
|
235
237
|
sass-rails (~> 5.0)
|
236
238
|
spring
|
237
239
|
spring-watcher-listen (~> 2.0.0)
|
238
240
|
sqlite3
|
239
|
-
synchromesh!
|
240
241
|
therubyracer
|
241
242
|
turbolinks (~> 5)
|
242
243
|
tzinfo-data
|
@@ -1,7 +1,9 @@
|
|
1
1
|
# app/views/components.rb
|
2
2
|
require 'opal'
|
3
3
|
require 'react'
|
4
|
-
require 'hyper-react'
|
4
|
+
#require 'hyper-react'
|
5
|
+
require 'hyper-mesh'
|
6
|
+
|
5
7
|
if React::IsomorphicHelpers.on_opal_client?
|
6
8
|
require 'opal-jquery'
|
7
9
|
require 'browser'
|
@@ -9,7 +11,7 @@ if React::IsomorphicHelpers.on_opal_client?
|
|
9
11
|
require 'browser/delay'
|
10
12
|
# add any additional requires that can ONLY run on client here
|
11
13
|
end
|
12
|
-
require '
|
14
|
+
require 'hyper-router'
|
13
15
|
require 'react_router'
|
14
16
|
require 'hyper-mesh'
|
15
17
|
require 'models'
|
@@ -0,0 +1,14 @@
|
|
1
|
+
#config/initializers/hyper_mesh.rb
|
2
|
+
|
3
|
+
#puts "***** #{__FILE__} ****"
|
4
|
+
#binding.pry
|
5
|
+
#File.join(Rails.root, 'app', 'models', 'public')
|
6
|
+
#binding.pry
|
7
|
+
#Opal.append_path File.join(Rails.root, 'app', 'models').untaint
|
8
|
+
|
9
|
+
|
10
|
+
|
11
|
+
HyperMesh.configuration do |config|
|
12
|
+
config.transport = :action_cable
|
13
|
+
config.channel_prefix = "synchromesh"
|
14
|
+
end
|
Binary file
|
data/lib/hyper-mesh.rb
CHANGED
@@ -49,11 +49,12 @@ else
|
|
49
49
|
require "reactive_record/serializers"
|
50
50
|
require "reactive_record/pry"
|
51
51
|
require_relative 'active_record_base'
|
52
|
-
require 'synchromesh/synchromesh_controller'
|
53
52
|
require 'hypermesh/version'
|
54
53
|
require 'synchromesh/connection'
|
55
54
|
require 'synchromesh/synchromesh'
|
56
55
|
require 'synchromesh/policy'
|
56
|
+
require 'synchromesh/synchromesh_controller'
|
57
|
+
|
57
58
|
Opal.append_path File.expand_path('../sources/', __FILE__).untaint
|
58
59
|
Opal.append_path File.expand_path('../', __FILE__).untaint
|
59
60
|
Opal.append_path File.expand_path('../../vendor', __FILE__).untaint
|
data/lib/hypermesh/version.rb
CHANGED
@@ -7,11 +7,11 @@ module ActiveRecord
|
|
7
7
|
def self.public_columns_hash
|
8
8
|
return @public_columns_hash if @public_columns_hash
|
9
9
|
Dir.glob(Rails.root.join('app/models/public/*.rb')).each do |file|
|
10
|
-
require_dependency(file)
|
10
|
+
require_dependency(file)
|
11
11
|
end
|
12
12
|
@public_columns_hash = {}
|
13
13
|
descendants.each do |model|
|
14
|
-
@public_columns_hash[model.name] = model.columns_hash
|
14
|
+
@public_columns_hash[model.name] = model.columns_hash rescue nil
|
15
15
|
end
|
16
16
|
@public_columns_hash
|
17
17
|
end
|
@@ -55,7 +55,7 @@ module ReactiveRecord
|
|
55
55
|
end
|
56
56
|
path = ::Rails.application.routes.routes.detect do |route|
|
57
57
|
# not sure why the second check is needed. It happens in the test app
|
58
|
-
route.app ==
|
58
|
+
route.app == HyperMesh::Engine or (route.app.respond_to?(:app) and route.app.app == HyperMesh::Engine)
|
59
59
|
end.path.spec
|
60
60
|
"<script type='text/javascript'>\n"+
|
61
61
|
"window.ReactiveRecordEnginePath = '#{path}';\n"+
|
@@ -6,24 +6,47 @@ module ReactiveRecord
|
|
6
6
|
end
|
7
7
|
end
|
8
8
|
|
9
|
+
module HyperMesh
|
10
|
+
class InternalPolicy
|
11
|
+
|
12
|
+
def self.accessible_attributes_for(model, acting_user)
|
13
|
+
user_channels = ClassConnectionRegulation.connections_for(acting_user, false) +
|
14
|
+
InstanceConnectionRegulation.connections_for(acting_user, false)
|
15
|
+
internal_policy = InternalPolicy.new(model, model.attribute_names, user_channels)
|
16
|
+
ChannelBroadcastRegulation.broadcast(internal_policy)
|
17
|
+
InstanceBroadcastRegulation.broadcast(model, internal_policy)
|
18
|
+
internal_policy.accessible_attributes_for
|
19
|
+
end
|
20
|
+
|
21
|
+
def accessible_attributes_for
|
22
|
+
accessible_attributes = Set.new
|
23
|
+
@channel_sets.each do |channel, attribute_set|
|
24
|
+
accessible_attributes.merge attribute_set
|
25
|
+
end
|
26
|
+
accessible_attributes << :id unless accessible_attributes.empty?
|
27
|
+
accessible_attributes
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
9
32
|
class ActiveRecord::Base
|
10
33
|
|
11
34
|
attr_accessor :acting_user
|
12
35
|
|
36
|
+
def view_permitted?(attribute)
|
37
|
+
HyperMesh::InternalPolicy.accessible_attributes_for(self, acting_user).include? attribute.to_sym
|
38
|
+
end
|
39
|
+
|
13
40
|
def create_permitted?
|
14
|
-
|
41
|
+
false
|
15
42
|
end
|
16
43
|
|
17
44
|
def update_permitted?
|
18
|
-
|
45
|
+
false
|
19
46
|
end
|
20
47
|
|
21
48
|
def destroy_permitted?
|
22
|
-
|
23
|
-
end
|
24
|
-
|
25
|
-
def view_permitted?(attribute)
|
26
|
-
true
|
49
|
+
false
|
27
50
|
end
|
28
51
|
|
29
52
|
def only_changed?(*attributes)
|