volt 0.9.4.pre1 → 0.9.4.pre2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +2 -0
  3. data/README.md +5 -5
  4. data/app/volt/models/user.rb +23 -20
  5. data/app/volt/tasks/user_tasks.rb +1 -14
  6. data/docs/UPGRADE_GUIDE.md +19 -0
  7. data/lib/volt/controllers/http_controller.rb +4 -4
  8. data/lib/volt/controllers/model_controller.rb +2 -2
  9. data/lib/volt/models/array_model.rb +39 -29
  10. data/lib/volt/models/associations.rb +7 -0
  11. data/lib/volt/models/buffer.rb +6 -4
  12. data/lib/volt/models/field_helpers.rb +4 -2
  13. data/lib/volt/models/model.rb +5 -3
  14. data/lib/volt/models/model_helpers/model_change_helpers.rb +15 -3
  15. data/lib/volt/models/model_helpers/model_helpers.rb +11 -0
  16. data/lib/volt/models/persistors/array_store.rb +8 -11
  17. data/lib/volt/models/persistors/model_store.rb +4 -14
  18. data/lib/volt/models/url.rb +2 -2
  19. data/lib/volt/page/bindings/event_binding.rb +12 -3
  20. data/lib/volt/page/bindings/view_binding/controller_handler.rb +2 -2
  21. data/lib/volt/server/component_templates.rb +8 -3
  22. data/lib/volt/{controllers/actions.rb → utils/lifecycle_callbacks.rb} +20 -16
  23. data/lib/volt/version.rb +1 -1
  24. data/lib/volt/volt/users.rb +29 -2
  25. data/spec/apps/kitchen_sink/app/main/controllers/main_controller.rb +12 -0
  26. data/spec/apps/kitchen_sink/app/main/models/user.rb +7 -0
  27. data/spec/apps/kitchen_sink/app/main/views/main/bindings.html +6 -0
  28. data/spec/integration/bindings_spec.rb +19 -0
  29. data/spec/models/associations_spec.rb +6 -6
  30. data/spec/models/buffer_spec.rb +19 -1
  31. data/spec/models/model_helpers/model_helpers_spec.rb +8 -0
  32. data/spec/models/url_spec.rb +16 -0
  33. data/spec/models/user_spec.rb +19 -20
  34. data/spec/models/user_validation_spec.rb +8 -0
  35. data/spec/{controllers/actions_spec.rb → utils/lifecycle_callbacks_spec.rb} +10 -10
  36. data/templates/project/app/main/models/user.rb +8 -0
  37. metadata +7 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 390291d6628b74ad144ac393109c5beab7ed1184
4
- data.tar.gz: c4cf1d8701aeddbcda38c2e87f03db1b3a4c9db9
3
+ metadata.gz: acec655ed08b959c64ad4fa1a1d70a8c202d6061
4
+ data.tar.gz: 16d6eba62257243a2ea66bacfafb64ebd4ece87a
5
5
  SHA512:
6
- metadata.gz: d31cb4efd187cb590793c53fb23bae2a9a3f53d4ab86090b22c4c611a97bdfb06444b43c630c19ecb266cd59e41a0e8655542de52f5377ea7b981412b914235a
7
- data.tar.gz: 8b5eca835a00949d4107b6257e8d18ffd34cf41514e94087e0a3e6aa2a0c04533861b318d2fed09ce7b20b2fe7930a8d5037bc59ad5da124c9ed16a6a885a02c
6
+ metadata.gz: 86d429de470cc56e6434e9fe0f0822d840bc4a9c8d9d2e1f686d18c01ba4bb7e709c4de84b4e474d2b9dd00c78968e5b69bb98fc804b038535892557e6b66212
7
+ data.tar.gz: f5cb8e59e91b908203d7bede6ae2fcaae6e84f091d65e5a61ca6b890a721b2e79edef6bc6bc31a887ac307c1c6b0d59596e4b7b4d1d8dc3c6d40a18b5d2d1984
data/CHANGELOG.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  ## 0.9.4.pre1
4
4
  ### Added
5
+ - ```root``` can now be called from a model to get the root model on the collection. (So if the model is on store, it will return ```store```)
6
+ - ```store``` can now be called from inside of a model
5
7
 
6
8
  ### Changed
7
9
  - fixed bug with ReactiveHash#to_json
data/README.md CHANGED
@@ -7,11 +7,11 @@
7
7
 
8
8
  For the current status of Volt, read: http://voltframework.com/blog
9
9
 
10
- Volt is a Ruby web framework where your Ruby code runs on both the server and the client (via [Opal](https://github.com/opal/opal)). The DOM automatically updates as the user interacts with the page. Page state can be stored in the URL. If the user hits a URL directly, the HTML will first be rendered on the server for faster load times and easier indexing by search engines.
10
+ Volt is a Ruby web framework where your Ruby code runs on both the server and the client (via [Opal](https://github.com/opal/opal)). The DOM automatically updates as the user interacts with the page. Page state can be stored in the URL. If the user hits a URL directly, the HTML will first be rendered on the server for faster load times and easier indexing by search engines. Subsequent local page interactions will be rendered on the client.
11
11
 
12
12
  Instead of syncing data between the client and server via HTTP, Volt uses a persistent connection between the client and server. When data is updated on one client, it is updated in the database and any other listening clients (with almost no setup code needed).
13
13
 
14
- Pages HTML is written in a template language where you can put Ruby between `{{` and `}}`. Volt uses data flow/reactive programming to automatically and intelligently propagate changes to the DOM (or any other code wanting to know when a value updates). When something in the DOM changes, Volt intelligently updates only the nodes that need to be changed.
14
+ Page HTML is written in a templating language where you can put Ruby between `{{` and `}}`. Volt uses data flow/reactive programming to automatically and intelligently propagate changes to the DOM (or to any other code that wants to know when a value has changed). When something in the DOM changes, Volt intelligently updates only the DOM nodes that need to be changed.
15
15
 
16
16
  See some demo videos here:
17
17
  - [Volt Todos Example](https://www.youtube.com/watch?v=KbFtIt7-ge8)
@@ -43,15 +43,15 @@ Rick Carlino has been putting together some great volt tutorial videos also.
43
43
 
44
44
  # Getting Help
45
45
 
46
- Have a question and need help? The volt team watches [stackoverflow](http://stackoverflow.com/search?q=voltrb) for questions and will get back to you quickly. Be sure to post the question with the #voltrb tag. If you have something you need to discuss, drop into our [gitter room](gitter.im/voltrb/volt).
46
+ Have a question and need help? The volt team watches [stackoverflow](http://stackoverflow.com/search?q=voltrb) for questions and will get back to you quickly. Be sure to post the question with the #voltrb tag. If you have something you'd like to discuss, drop into our [gitter room](https://gitter.im/voltrb/volt).
47
47
 
48
48
  # Contributing
49
49
 
50
- You want to contribute? Great! Thanks for being awesome! At the moment, we have a big internal todo list, hop on https://gitter.im/voltrb/volt so we don't duplicate work. Pull requests are always welcome, but asking about helping on Gitter should save some duplication.
50
+ You want to contribute? Great! Thanks for being awesome! At the moment, we have a big internal todo list. Please hop on https://gitter.im/voltrb/volt so that we don't duplicate work. Pull requests are always welcome, but asking about helping on Gitter should save some duplication.
51
51
 
52
52
  # Support
53
53
 
54
- VoltFramework is currently a labor of love mainly built by a small group of core developers. Donations are always welcome and will help Volt get to production faster :-) Also, if you or your company is interested in sponsoring Volt, please talk to @ryanstout in the [gitter](https://gitter.im/voltrb/volt)
54
+ VoltFramework is currently a labor of love mainly built by a small group of core developers. Donations are always welcome and will help Volt get to production faster :-) Also, if you or your company is interested in sponsoring Volt, please talk to @ryanstout in [gitter](https://gitter.im/voltrb/volt).
55
55
 
56
56
  [![Pledgie](https://pledgie.com/campaigns/26731.png?skin_name=chrome)](https://pledgie.com/campaigns/26731)
57
57
 
@@ -2,9 +2,6 @@ require 'bcrypt' unless RUBY_PLATFORM == 'opal'
2
2
 
3
3
  module Volt
4
4
  class User < Model
5
- field :username
6
- field :email
7
- field :name
8
5
  field :password
9
6
 
10
7
  # returns login field name depending on config settings
@@ -16,9 +13,6 @@ module Volt
16
13
  end
17
14
  end
18
15
 
19
- validate login_field, unique: true, length: 8
20
- validate :email, email: true
21
-
22
16
  permissions(:read) do
23
17
  # Never pass the hashed_password to the client
24
18
  deny :hashed_password
@@ -27,23 +21,32 @@ module Volt
27
21
  deny if !id == Volt.current_user_id && !new?
28
22
  end
29
23
 
30
- if RUBY_PLATFORM == 'opal'
31
- validations do
32
- # Only validate password when it has changed
33
- if changed?(:password)
34
- # Don't validate on the server
35
- validate :password, length: 8
36
- end
24
+ unless RUBY_PLATFORM == 'opal'
25
+ permissions(:update) do
26
+ deny unless id == Volt.current_user_id
37
27
  end
38
28
  end
39
29
 
40
- def password=(val)
41
- if Volt.server?
42
- # on the server, we bcrypt the password and store the result
43
- self._hashed_password = BCrypt::Password.create(val)
44
- else
45
- # Assign the attribute
46
- self._password = val
30
+ validations do
31
+ # Only validate password when it has changed
32
+ if changed?(:password)
33
+ # Don't validate on the server
34
+ validate :password, length: 8
35
+ end
36
+ end
37
+
38
+ # On the server, we hash the password and remove it (so we just store the hash)
39
+ unless RUBY_PLATFORM == 'opal'
40
+ before_save :hash_password
41
+
42
+ def hash_password
43
+ password = get('password')
44
+
45
+ # Clear the password
46
+ set('password', nil)
47
+
48
+ # Set the hashed_password field instead
49
+ set('hashed_password', BCrypt::Password.create(password))
47
50
  end
48
51
  end
49
52
  end
@@ -16,22 +16,9 @@ class UserTasks < Volt::Task
16
16
 
17
17
  match_pass = BCrypt::Password.new(user._hashed_password)
18
18
  fail 'Password did not match' unless match_pass == password
19
- fail 'app_secret is not configured' unless Volt.config.app_secret
20
19
 
21
- # TODO: returning here should be possible, but causes some issues
22
- # Salt the user id with the app_secret so the end user can't
23
- # tamper with the cookie
24
- signature = Digest::SHA256.hexdigest(salty_user_id(user.id))
25
-
26
- # Return user_id:hash on user id
27
- next "#{user.id}:#{signature}"
20
+ next Volt.user_login_signature(user)
28
21
  end
29
22
  end
30
23
  end
31
-
32
- private
33
-
34
- def salty_user_id(user_id)
35
- "#{Volt.config.app_secret}::#{user_id}"
36
- end
37
24
  end
@@ -1,3 +1,22 @@
1
+ # 0.9.3 to 0.9.4
2
+
3
+ We moved logic out of Volt::User and into the generated user file, so it is easier to customize. Add the following to your app/main/models/user.rb:
4
+
5
+ ```ruby
6
+ # The login_field method returns the name that should be used for the field
7
+ # where the users e-mail is stored. (usually :username or :email)
8
+ def self.login_field
9
+ :email
10
+ end
11
+
12
+ # login_field is set to :email by default and can be set to
13
+ field login_field
14
+ field :name
15
+
16
+ validate login_field, unique: true, length: 8
17
+ validate :email, email: true
18
+ ```
19
+
1
20
  # 0.9.2 to 0.9.3
2
21
 
3
22
  Upgrading from 0.9.2 should be fairly simple, just implement the following:
@@ -1,11 +1,11 @@
1
1
  require 'volt/server/rack/http_response_header'
2
2
  require 'volt/server/rack/http_response_renderer'
3
- require 'volt/controllers/actions'
3
+ require 'volt/utils/lifecycle_callbacks'
4
4
 
5
5
  module Volt
6
6
  # Allow you to create controllers that act as http endpoints
7
7
  class HttpController
8
- include Actions
8
+ include LifecycleCallbacks
9
9
 
10
10
  #TODO params is only public for testing
11
11
  attr_accessor :params
@@ -23,9 +23,9 @@ module Volt
23
23
  end
24
24
 
25
25
  def perform(action='index')
26
- filtered = run_actions(:before_action, action)
26
+ filtered = run_callbacks(:before_action, action)
27
27
  send(action.to_sym) unless filtered
28
- run_actions(:after_action, action) unless filtered
28
+ run_callbacks(:after_action, action) unless filtered
29
29
  respond
30
30
  end
31
31
 
@@ -1,12 +1,12 @@
1
1
  require 'volt/reactive/reactive_accessors'
2
- require 'volt/controllers/actions'
2
+ require 'volt/utils/lifecycle_callbacks'
3
3
  require 'volt/controllers/template_helpers'
4
4
  require 'volt/controllers/collection_helpers'
5
5
 
6
6
  module Volt
7
7
  class ModelController
8
8
  include ReactiveAccessors
9
- include Actions
9
+ include LifecycleCallbacks
10
10
  include CollectionHelpers
11
11
 
12
12
  # A model controller will have its
@@ -94,40 +94,50 @@ module Volt
94
94
  model = wrap_values([model]).first
95
95
  end
96
96
 
97
- if model.is_a?(Model) && !model.can_create?
98
- fail "permissions did not allow create for #{model.inspect}"
99
- end
100
97
 
101
- # Add it to the array and trigger any watches or on events.
102
- super(model)
98
+ if model.is_a?(Model)
99
+ if !model.can_create?
100
+ fail "permissions did not allow create for #{model.inspect}"
101
+ end
103
102
 
104
- if @persistor
105
- promise = @persistor.added(model, @array.size - 1)
106
- if promise && promise.is_a?(Promise)
107
- return promise.then do
108
-
109
- # Mark the model as not new
110
- model.instance_variable_set('@new', false)
111
-
112
- # Mark the model as loaded
113
- model.change_state_to(:loaded_state, :loaded)
114
-
115
- # return the model
116
- model
117
- end.fail do |err|
118
- # remove from the collection because it failed to save on the server
119
- # we don't need to call delete on the server.
120
- index = @array.index(model)
121
- delete_at(index, true)
122
-
123
- # re-raise, err might not be an Error object, so we use a rejected promise to re-raise
124
- Promise.new.reject(err)
125
- end
103
+ # Add it to the array and trigger any watches or on events.
104
+ super(model)
105
+
106
+ @persistor.added(model, @array.size - 1)
107
+
108
+ # Validate and save
109
+ promise = model.run_changed.then do
110
+ # Mark the model as not new
111
+ model.instance_variable_set('@new', false)
112
+
113
+ # Mark the model as loaded
114
+ model.change_state_to(:loaded_state, :loaded)
115
+
116
+ end.fail do |err|
117
+ # remove from the collection because it failed to save on the server
118
+ # we don't need to call delete on the server.
119
+ index = @array.index(model)
120
+ delete_at(index, true)
121
+
122
+ # remove from the id list
123
+ @persistor.try(:remove_tracking_id, model)
124
+
125
+ # re-raise, err might not be an Error object, so we use a rejected promise to re-raise
126
+ Promise.new.reject(err)
127
+ end
128
+ else
129
+ promise = nil.then do
130
+ # Add it to the array and trigger any watches or on events.
131
+ super(model)
132
+
133
+ @persistor.added(model, @array.size - 1)
126
134
  end
127
135
  end
128
136
 
129
- # Return this model
130
- Promise.new.resolve(model)
137
+ promise.then do
138
+ # resolve the model
139
+ model
140
+ end
131
141
  end
132
142
 
133
143
  # Works like << except it always returns a promise
@@ -16,6 +16,13 @@ module Volt
16
16
  root.get(method_name.pluralize).where(id: lookup_key).first
17
17
  end
18
18
  end
19
+
20
+ define_method(:"#{method_name}=") do |obj|
21
+ id = obj.is_a?(Fixnum) ? obj : obj.id
22
+
23
+ # Assign the current model's something_id to the object's id
24
+ set("#{method_name}_id", id)
25
+ end
19
26
  end
20
27
 
21
28
  def has_many(method_name, remote_key_name = nil)
@@ -18,7 +18,7 @@ module Volt
18
18
  if save_to
19
19
  if save_to.is_a?(ArrayModel)
20
20
  # Add to the collection
21
- promise = save_to.append(attributes)
21
+ promise = save_to.create(attributes)
22
22
  else
23
23
  # We have a saved model
24
24
  promise = save_to.assign_attributes(attributes)
@@ -33,11 +33,13 @@ module Volt
33
33
  new_model.change_state_to(:loaded_state, :loaded)
34
34
 
35
35
  # Set the buffer's id to track the main model's id
36
- attributes[:id] = new_model.id
37
- options[:save_to] = new_model
36
+ options[:save_to] = new_model
38
37
  end
39
38
 
40
- nil
39
+ # Copy attributes back from save_to model
40
+ @attributes = new_model.attributes
41
+
42
+ new_model
41
43
  end.fail do |errors|
42
44
  if errors.is_a?(Hash)
43
45
  server_errors.replace(errors)
@@ -11,8 +11,10 @@ module FieldHelpers
11
11
  end
12
12
 
13
13
  if klass
14
- # Add type validation
15
- validate name, type: klass
14
+ # Add type validation, execpt for String, since anything can be a string.
15
+ unless klass == String
16
+ validate name, type: klass
17
+ end
16
18
  end
17
19
 
18
20
  define_method(name) do
@@ -18,6 +18,7 @@ require 'volt/models/associations'
18
18
  require 'volt/reactive/class_eventable'
19
19
  require 'volt/utils/event_counter'
20
20
  require 'volt/reactive/reactive_accessors'
21
+ require 'volt/utils/lifecycle_callbacks'
21
22
  require 'thread'
22
23
 
23
24
  module Volt
@@ -30,6 +31,7 @@ module Volt
30
31
  end
31
32
 
32
33
  class Model
34
+ include LifecycleCallbacks
33
35
  include ModelWrapper
34
36
  include ModelHelpers
35
37
  include ModelHashBehaviour
@@ -357,14 +359,14 @@ module Volt
357
359
  if initial_setup
358
360
  # Run initial validation
359
361
  if Volt.in_mode?(:no_validate)
360
- # No validate, resolve nil
361
- Promise.new.resolve(nil)
362
+ # No validate, resolve self
363
+ Promise.new.resolve(self)
362
364
  else
363
365
  return validate!.then do |errs|
364
366
  if errs && errs.size > 0
365
367
  Promise.new.reject(errs)
366
368
  else
367
- Promise.new.resolve(nil)
369
+ Promise.new.resolve(self)
368
370
  end
369
371
  end
370
372
  end
@@ -3,7 +3,9 @@
3
3
 
4
4
  module Volt
5
5
  module ModelChangeHelpers
6
- private
6
+ def self.included(base)
7
+ base.setup_action_helpers_in_class(:before_save, :before_validate)
8
+ end
7
9
 
8
10
  # Called when something in the model changes. Saves
9
11
  # the model if there is a persistor, and changes the
@@ -38,14 +40,16 @@ module Volt
38
40
  end
39
41
 
40
42
  # Return result inside of the validate! promise
41
- result
43
+ result.then { self }
42
44
  end
43
45
  end
44
46
 
45
47
  # Didn't run validations
46
- nil
48
+ self.then
47
49
  end
48
50
 
51
+
52
+ private
49
53
  # Should only be called from run_changed. Saves the changes back to the persistor
50
54
  # and clears the tracked changes.
51
55
  def persist_changes(attribute_name)
@@ -53,6 +57,14 @@ module Volt
53
57
 
54
58
  # Don't save right now if we're in a nosave block
55
59
  unless Volt.in_mode?(:no_save)
60
+ # Call the before_save callback
61
+ # skip validations when running before_save, this prevents n+1, and allows
62
+ # the before_save to put the model into an invalid state, which you want
63
+ # sometimes.
64
+ Volt::Model.no_validate do
65
+ run_callbacks(:before_save)
66
+ end
67
+
56
68
  # the changed method on a persistor should return a promise that will
57
69
  # be resolved when the save is complete, or fail with a hash of errors.
58
70
  if @persistor
@@ -58,6 +58,17 @@ module Volt
58
58
  end
59
59
  end
60
60
 
61
+ def store
62
+ # TODO: Move away from $page
63
+ $page.store
64
+ end
65
+
66
+ # returns the root model for the collection the model is currently on. So
67
+ # if the model is persisted somewhere on store, it will return ```store```
68
+ def root
69
+ persistor.try(:root_model)
70
+ end
71
+
61
72
 
62
73
 
63
74
 
@@ -287,24 +287,14 @@ module Volt
287
287
  # Called when the client adds an item.
288
288
  def added(model, index)
289
289
  if model.persistor
290
- # Tell the persistor it was added, return the promise
291
- promise = model.persistor.add_to_collection
292
-
293
290
  # Track the the model got added
294
291
  @ids[model.id] = true
295
-
296
- promise
297
292
  end
298
293
  end
299
294
 
300
295
  # Called when the client removes an item
301
296
  def removed(model)
302
- if model.persistor
303
- # Tell the persistor it was removed
304
- model.persistor.remove_from_collection
305
-
306
- @ids.delete(model.id)
307
- end
297
+ remove_tracking_id(model)
308
298
 
309
299
  if defined?($loading_models) && $loading_models
310
300
  return
@@ -312,6 +302,13 @@ module Volt
312
302
  StoreTasks.delete(channel_name, model.attributes[:id])
313
303
  end
314
304
  end
305
+
306
+ def remove_tracking_id(model)
307
+ if model.persistor
308
+ # Tell the persistor it was removed
309
+ @ids.delete(model.id)
310
+ end
311
+ end
315
312
  end
316
313
  end
317
314
  end
@@ -21,18 +21,6 @@ module Volt
21
21
  model.change_state_to(:loaded_state, initial_state)
22
22
  end
23
23
 
24
- def add_to_collection
25
- @in_collection = true
26
- ensure_setup
27
-
28
- # Call changed, return the promise
29
- changed
30
- end
31
-
32
- def remove_from_collection
33
- @in_collection = false
34
- end
35
-
36
24
  def auto_generate_id
37
25
  true
38
26
  end
@@ -68,8 +56,10 @@ module Volt
68
56
 
69
57
  path_size = path.size
70
58
  if save_changes? && path_size > 0 && !@model.nil?
71
- if path_size > 3 && (parent = @model.parent) && (source = parent.parent)
72
- @model.attributes[:"#{path[-4].singularize}_id"] = source.id
59
+ if path_size > 3 && (parent = @model.parent)
60
+ # If we have a collection, go up one more.
61
+ parent = parent.parent unless parent.is_a?(Volt::Model)
62
+ @model.attributes[:"#{path[-4].singularize}_id"] = parent.id
73
63
  end
74
64
 
75
65
  if !collection
@@ -97,8 +97,8 @@ module Volt
97
97
  new_url
98
98
  end
99
99
 
100
- def url_with(params)
101
- url_for(params.to_h.merge(params))
100
+ def url_with(passed_params)
101
+ url_for(params.to_h.merge(passed_params))
102
102
  end
103
103
 
104
104
  # Called when the state has changed and the url in the
@@ -31,7 +31,17 @@ module Volt
31
31
 
32
32
  def initialize(volt_app, target, context, binding_name, event_name, call_proc)
33
33
  super(volt_app, target, context, binding_name)
34
- @event_name = event_name
34
+
35
+ # Map blur/focus to focusout/focusin
36
+ @event_name = case event_name
37
+ when 'blur'
38
+ 'focusout'
39
+ when 'focus'
40
+ 'focusin'
41
+ else
42
+ event_name
43
+ end
44
+
35
45
 
36
46
  handler = proc do |js_event|
37
47
  event = JSEvent.new(js_event)
@@ -55,8 +65,7 @@ module Volt
55
65
  # end
56
66
 
57
67
  end
58
-
59
- @listener = page.events.add(event_name, self, handler)
68
+ @listener = page.events.add(@event_name, self, handler)
60
69
  end
61
70
 
62
71
  # Remove the event binding
@@ -25,7 +25,7 @@ module Volt
25
25
  # If no stage, then we are calling the main action method,
26
26
  # so we should call the before/after actions
27
27
  unless has_stage
28
- if @controller.run_actions(:before_action, @action)
28
+ if @controller.run_callbacks(:before_action, @action)
29
29
  # stop_chain was called
30
30
  return true
31
31
  end
@@ -33,7 +33,7 @@ module Volt
33
33
 
34
34
  @controller.send(method_name) if @controller.respond_to?(method_name)
35
35
 
36
- @controller.run_actions(:after_action, @action) unless has_stage
36
+ @controller.run_callbacks(:after_action, @action) unless has_stage
37
37
 
38
38
  # before_action chain was not stopped
39
39
  false
@@ -200,11 +200,16 @@ module Volt
200
200
  end
201
201
 
202
202
  def generate_initializers_code
203
- code = "\nrequire_tree '#{@component_path}/config/initializers/'\n"
204
- code << "require_tree '#{@component_path}/config/initializers/client'\n"
203
+ paths = Dir["#{@component_path}/config/initializers/*.rb"]
204
+ paths += Dir["#{@component_path}/config/initializers/client/*.rb"]
205
+
206
+ cpath_size = @component_path.size
207
+ paths.map! {|path| @component_name + path[cpath_size..-1]}
208
+
209
+ code = "\n" + paths.map { |path| "require '#{path}'" }.join("\n")
205
210
 
206
211
  code
207
212
  end
208
213
 
209
214
  end
210
- end
215
+ end
@@ -9,23 +9,24 @@
9
9
  #
10
10
  # before_action :require_login
11
11
  module Volt
12
- module Actions
12
+ module LifecycleCallbacks
13
13
  # StopChainException inherits from Exception directly so it will not be handled by a
14
14
  # default rescue.
15
15
  class StopChainException < Exception; end
16
16
 
17
17
  module ClassMethods
18
- # Takes a list of action groups (as symbols). An action group is typically used for before/after, but
19
- # can be used anytime you have multiple sets of actions. The method will create an {x}_action method for each
20
- # group passed in.
21
- def setup_action_helpers_in_class(*groups)
22
- groups.each do |group|
18
+ # Takes a list of callbacks (as symbols). A callback is typically used
19
+ # for before/after actions, but can be used anytime you have callbacks
20
+ # that may be filtered by action. The method will create an callback
21
+ # method for each callback name passed in.
22
+ def setup_action_helpers_in_class(*callback_names)
23
+ callback_names.each do |callback_name|
23
24
  # Setup a class attribute to track the
24
- callbacks_var_name = :"#{group}_callbacks"
25
+ callbacks_var_name = :"#{callback_name}_callbacks"
25
26
  class_attribute(callbacks_var_name)
26
27
 
27
28
  # Create the method on the class
28
- define_singleton_method(group) do |*args, &block|
29
+ define_singleton_method(callback_name) do |*args, &block|
29
30
  # Add the block in place of the symbol
30
31
  args.unshift(block) if block
31
32
 
@@ -52,16 +53,19 @@ module Volt
52
53
  end
53
54
  end
54
55
 
55
- # To run the actions on a class, call #run_actions passing in the group
56
- # and the action being called on. If the callback chain was stopped with
57
- # #stop_chain, it will return true, otherwise false.
58
- def run_actions(group, action)
59
- callbacks = self.class.send(:"#{group}_callbacks")
56
+ # To run the callbacks on a class, call #run_callbacks passing in the
57
+ # callback_name and the action it runs with. If the callback chain was
58
+ # stopped with #stop_chain, it will return true, otherwise false.
59
+ def run_callbacks(callback_name, action=nil)
60
+ callbacks = self.class.send(:"#{callback_name}_callbacks")
60
61
 
61
- filtered_callbacks = filter_actions_by_only_exclude(callbacks || [], action)
62
+ callbacks ||= []
63
+ if action
64
+ callbacks = filter_actions_by_only_exclude(callbacks || [], action)
65
+ end
62
66
 
63
67
  begin
64
- filtered_callbacks.each do |callback|
68
+ callbacks.map { |v| v[0] }.each do |callback|
65
69
  case callback
66
70
  when Symbol
67
71
  send(callback)
@@ -100,7 +104,7 @@ module Volt
100
104
  # If no only, include it
101
105
  true
102
106
  end
103
- end.map { |v| v[0] }
107
+ end
104
108
  end
105
109
  end
106
110
  end
data/lib/volt/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module Volt
2
2
  module Version
3
- STRING = '0.9.4.pre1'
3
+ STRING = '0.9.4.pre2'
4
4
  end
5
5
  end
@@ -39,8 +39,11 @@ module Volt
39
39
 
40
40
  # as_user lets you run a block as another user
41
41
  #
42
- # @param user_id [Integer]
43
- def as_user(user_id)
42
+ # @param user_or_user_id [Integer|Volt::Model]
43
+ def as_user(user_or_id)
44
+ # if we have a user, get the id
45
+ user_id = user_or_id.is_a?(Volt::Model) ? user_or_id.id : user_or_id
46
+
44
47
  previous_id = Thread.current['with_user_id']
45
48
  Thread.current['with_user_id'] = user_id
46
49
 
@@ -49,6 +52,22 @@ module Volt
49
52
  Thread.current['with_user_id'] = previous_id
50
53
  end
51
54
 
55
+ unless RUBY_PLATFORM == 'opal'
56
+ # Takes a user and returns a signed string that can be used for the
57
+ # user_id cookie to login a user.
58
+ def user_login_signature(user)
59
+ fail 'app_secret is not configured' unless Volt.config.app_secret
60
+
61
+ # TODO: returning here should be possible, but causes some issues
62
+ # Salt the user id with the app_secret so the end user can't
63
+ # tamper with the cookie
64
+ signature = Digest::SHA256.hexdigest(salty_user_id(user.id))
65
+
66
+ # Return user_id:hash on user id
67
+ "#{user.id}:#{signature}"
68
+ end
69
+ end
70
+
52
71
  def skip_permissions
53
72
  Volt.run_in_mode(:skip_permissions) do
54
73
  yield
@@ -115,5 +134,13 @@ module Volt
115
134
 
116
135
  user_id_signature
117
136
  end
137
+
138
+
139
+ private
140
+ unless RUBY_PLATFORM == 'opal'
141
+ def salty_user_id(user_id)
142
+ "#{Volt.config.app_secret}::#{user_id}"
143
+ end
144
+ end
118
145
  end
119
146
  end
@@ -5,6 +5,8 @@ module Main
5
5
  class MainController < Volt::ModelController
6
6
  model :page
7
7
 
8
+ reactive_accessor :blur_count, :focus_count
9
+
8
10
  def index
9
11
  a = {}
10
12
  a[{}] = 5
@@ -76,6 +78,16 @@ module Main
76
78
  page._items = changes
77
79
  end
78
80
 
81
+ def blur
82
+ self.blur_count ||= 0
83
+ self.blur_count += 1
84
+ end
85
+
86
+ def focus
87
+ self.focus_count ||= 0
88
+ self.focus_count += 1
89
+ end
90
+
79
91
  private
80
92
 
81
93
  # the main template contains a #template binding that shows another
@@ -1,2 +1,9 @@
1
1
  class User < Volt::User
2
+ # login_field is set to :email by default and can be changed to :username
3
+ # in config/app.rb
4
+ field login_field
5
+ field :name
6
+
7
+ validate login_field, unique: true, length: 8
8
+ validate :email, email: true
2
9
  end
@@ -148,4 +148,10 @@
148
148
 
149
149
  <p id="escapeContent">{{{this is {{escaped}}}}}</p>
150
150
 
151
+ <h2>Event</h2>
152
+
153
+ <input e-blur="blur" e-focus="focus" id="blurFocusField" />
154
+ <div id="blurCount">{{ blur_count || 0 }}</div>
155
+ <div id="focusCount">{{ focus_count || 0 }}</div>
156
+
151
157
 
@@ -243,6 +243,25 @@ describe 'bindings test', type: :feature, sauce: true do
243
243
  end
244
244
  end
245
245
 
246
+ # NOTE: For some reason this spec fails randomly (capybara issue I think)
247
+ # describe "events" do
248
+ # it 'should handle focus and blur' do
249
+ # visit '/'
250
+ # click_link 'Bindings'
251
+
252
+ # expect(find('#focusCount')).to have_content('0')
253
+ # expect(find('#blurCount')).to have_content('0')
254
+
255
+ # page.execute_script("$('#blurFocusField').focus()")
256
+ # sleep 0.1
257
+ # expect(find('#focusCount')).to have_content('1')
258
+
259
+ # page.execute_script("$('#blurFocusField').blur()")
260
+ # expect(find('#blurCount')).to have_content('1')
261
+
262
+ # end
263
+ # end
264
+
246
265
  if ENV['BROWSER'] != 'phantom'
247
266
  describe 'input hidden and select' do
248
267
  it 'should display binding value' do
@@ -17,14 +17,14 @@ describe Volt::Associations do
17
17
  if RUBY_PLATFORM != 'opal'
18
18
  describe "with samples" do
19
19
  before do
20
- store._people! << { name: 'Jimmy' }
21
- @person = store._people[0].sync
22
- @person._addresses! << { city: 'Bozeman' }
23
- @person._addresses << { city: 'Portland' }
20
+ @person = Person.new(name: 'Jimmy')
21
+ store.people << @person
22
+ @person.addresses << Address.new(city: 'Bozeman')
23
+ @person.addresses << Address.new(city: 'Portland')
24
24
  end
25
25
 
26
26
  it 'should associate via belongs_to' do
27
- address = store._addresses!.first.sync
27
+ address = store.addresses.first.sync
28
28
 
29
29
  expect(address.person.sync.id).to eq(@person.id)
30
30
  end
@@ -64,7 +64,7 @@ describe Volt::Associations do
64
64
 
65
65
  it 'should assign the reference_id for has_many' do
66
66
  bob = store.people.create.sync
67
- bob.addresses.create({:street => '1234 awesome street'})
67
+ address = bob.addresses.create({:street => '1234 awesome street'})
68
68
  expect(bob.addresses[0].sync.person_id).to eq(bob.id)
69
69
  expect(bob.id).to_not eq(nil)
70
70
  end
@@ -4,9 +4,18 @@ class ::TestSaveFailure < Volt::Model
4
4
  validate :name, length: 5
5
5
  end
6
6
 
7
+ class ::TestChangedAttribute < Volt::Model
8
+ before_save :change_attributes
9
+
10
+ def change_attributes
11
+ set('one', 1)
12
+ set('two', 2)
13
+ end
14
+ end
15
+
7
16
  describe Volt::Buffer do
8
17
  it 'should let you pass a block that evaluates to the then of the promise' do
9
- buffer = the_page._items!.buffer
18
+ buffer = the_page._items.buffer
10
19
 
11
20
  count = 0
12
21
  result = buffer.save! do
@@ -18,6 +27,15 @@ describe Volt::Buffer do
18
27
  end
19
28
 
20
29
  if RUBY_PLATFORM != 'opal'
30
+ it 'should copy attributes back from the save_to model incase it changes them during save' do
31
+ buffer = the_page._test_changed_attributes.buffer
32
+
33
+ buffer.save!.sync
34
+ expect(buffer.save_to.attributes.without(:id)).to eq({one: 1, two: 2})
35
+ expect(buffer.attributes.without(:id)).to eq({one: 1, two: 2})
36
+ expect(buffer.id).to eq(buffer.save_to.id)
37
+ end
38
+
21
39
  it 'should reject a failed save! with the errors object' do
22
40
  buffer = the_page._test_save_failures.buffer
23
41
 
@@ -0,0 +1,8 @@
1
+ require 'spec_helper'
2
+
3
+ describe Volt::ModelHelpers do
4
+ # it 'should have a root method on models that returns the root collection' do
5
+ # model = store._posts.create
6
+ # expect(model.root).to eq(store)
7
+ # end
8
+ end
@@ -63,4 +63,20 @@ describe Volt::URL do
63
63
  expect(subject.url_for params).to eq uri
64
64
  end
65
65
  end
66
+
67
+ describe '#url_with' do
68
+ let(:uri) { 'http://voltframework.com:8888/path/1?query=val&page=1#fragment' }
69
+ let(:fake_router) do
70
+ router = Volt::Routes.new
71
+
72
+ router.define do
73
+ client '/path/{{ id }}', view: 'blog/show'
74
+ end
75
+ end
76
+
77
+ it 'regenerates the URL and merges the given params' do
78
+ params = { page: 1 }
79
+ expect(subject.url_with params).to eq uri
80
+ end
81
+ end
66
82
  end
@@ -37,9 +37,7 @@ describe Volt::User do
37
37
  end
38
38
 
39
39
  describe '#password=' do
40
- let!(:user) { Volt::User.new }
41
-
42
- subject { user.password = 'test' }
40
+ subject { Volt::User.new(password: 'test') }
43
41
 
44
42
  if RUBY_PLATFORM != 'opal'
45
43
  describe 'when it is a Volt server' do
@@ -49,34 +47,37 @@ describe Volt::User do
49
47
  end
50
48
 
51
49
  it 'encrypts password' do
52
- subject
50
+ the_page._users << subject
53
51
 
54
52
  expect(BCrypt::Password).to have_received :create
55
53
  end
56
54
 
57
55
  it 'sets _hashed_password to passed value' do
58
- subject
56
+ the_page._users << subject
59
57
 
60
- expect(user._hashed_password).to eq 'hashed-password'
58
+ expect(subject.get('hashed_password')).to eq 'hashed-password'
61
59
  end
62
60
  end
63
61
 
64
62
  it 'should allow updates without validating the password' do
65
63
  bob = store._users.buffer(name: 'Bob', email: 'bob@bob.com', password: '39sdjkdf932jklsd')
66
- bob.save!
64
+ bob.save!.sync
67
65
 
68
- expect(bob._password).to eq(nil)
66
+ Volt.as_user(bob) do
69
67
 
70
- bob_buf = bob.buffer
68
+ expect(bob.password).to eq(nil)
71
69
 
72
- bob_buf._name = 'Jimmy'
70
+ bob_buf = bob.buffer
73
71
 
74
- saved = false
75
- bob_buf.save! do
76
- saved = true
77
- end
72
+ bob_buf.name = 'Jimmy'
73
+
74
+ saved = false
75
+ bob_buf.save! do
76
+ saved = true
77
+ end
78
78
 
79
- expect(saved).to eq(true)
79
+ expect(saved).to eq(true)
80
+ end
80
81
  end
81
82
  end
82
83
 
@@ -87,12 +88,10 @@ describe Volt::User do
87
88
  allow(Volt).to receive(:server?).and_return false
88
89
  end
89
90
 
90
- subject { user.password = 'a valid test password' }
91
-
92
- it 'sets _password to passed value' do
93
- subject
91
+ subject { Volt::User.new(password: 'a valid test password') }
94
92
 
95
- expect(user._password).to eq('a valid test password')
93
+ it 'sets password to passed value' do
94
+ expect(subject.password).to eq('a valid test password')
96
95
  end
97
96
  end
98
97
  end
@@ -1,5 +1,13 @@
1
1
  require 'spec_helper'
2
2
 
3
+ class TestUserTodo < Volt::Model
4
+ own_by_user
5
+
6
+ permissions(:update) do
7
+ deny :user_id
8
+ end
9
+ end
10
+
3
11
  describe Volt::UserValidatorHelpers do
4
12
  context 'with user' do
5
13
  before do
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
  require 'volt/controllers/model_controller'
3
3
 
4
4
  class BaseTestActions
5
- include Volt::Actions
5
+ include Volt::LifecycleCallbacks
6
6
 
7
7
  setup_action_helpers_in_class(:before_action, :after_action)
8
8
  end
@@ -70,14 +70,14 @@ class TestOnlyCallbacks < TestActionsSymbolsBase
70
70
  before_action :run_one, :run_two, only: [:new]
71
71
  end
72
72
 
73
- describe Volt::Actions do
73
+ describe Volt::LifecycleCallbacks do
74
74
  it 'should trigger before actions via blocks' do
75
75
  test_class = TestActionsBlocks.new
76
76
 
77
77
  expect(test_class.ran_before1).to eq(nil)
78
78
  expect(test_class.ran_before2).to eq(nil)
79
79
 
80
- test_class.run_actions(:before_action, :index)
80
+ test_class.run_callbacks(:before_action, :index)
81
81
 
82
82
  expect(test_class.ran_before1).to eq(true)
83
83
  expect(test_class.ran_before2).to eq(true)
@@ -89,7 +89,7 @@ describe Volt::Actions do
89
89
  expect(test_class.ran_one).to eq(nil)
90
90
  expect(test_class.ran_two).to eq(nil)
91
91
 
92
- test_class.run_actions(:before_action, :index)
92
+ test_class.run_callbacks(:before_action, :index)
93
93
 
94
94
  expect(test_class.ran_one).to eq(true)
95
95
  expect(test_class.ran_two).to eq(true)
@@ -107,17 +107,17 @@ describe Volt::Actions do
107
107
  expect(test_class.ran_one).to eq(nil)
108
108
  expect(test_class.ran_two).to eq(nil)
109
109
 
110
- result = test_class.run_actions(:before_action, :index)
110
+ result = test_class.run_callbacks(:before_action, :index)
111
111
  expect(result).to eq(false)
112
112
 
113
113
  expect(test_class.ran_one).to eq(true)
114
114
  expect(test_class.ran_two).to eq(true)
115
115
  end
116
116
 
117
- it 'should stop the chain when #stop_chain is called and return false from #run_actions' do
117
+ it 'should stop the chain when #stop_chain is called and return false from #run_callbacks' do
118
118
  test_class = TestStopCallbacks.new
119
119
 
120
- result = test_class.run_actions(:before_action, :index)
120
+ result = test_class.run_callbacks(:before_action, :index)
121
121
  expect(result).to eq(true)
122
122
 
123
123
  expect(test_class.ran_one).to eq(true)
@@ -129,18 +129,18 @@ describe Volt::Actions do
129
129
  it 'should call without any callbacks' do
130
130
  test_class = TestNoCallbacks.new
131
131
 
132
- result = test_class.run_actions(:before_action, :index)
132
+ result = test_class.run_callbacks(:before_action, :index)
133
133
  expect(result).to eq(false)
134
134
  end
135
135
 
136
136
  it 'should follow only limitations' do
137
137
  test_only = TestOnlyCallbacks.new
138
138
 
139
- test_only.run_actions(:before_action, :index)
139
+ test_only.run_callbacks(:before_action, :index)
140
140
  expect(test_only.ran_one).to eq(nil)
141
141
  expect(test_only.ran_two).to eq(nil)
142
142
 
143
- test_only.run_actions(:before_action, :new)
143
+ test_only.run_callbacks(:before_action, :new)
144
144
  expect(test_only.ran_one).to eq(true)
145
145
  expect(test_only.ran_two).to eq(true)
146
146
  end
@@ -1,4 +1,12 @@
1
1
  # By default Volt generates this User model which inherits from Volt::User,
2
2
  # you can rename this if you want.
3
3
  class User < Volt::User
4
+ # login_field is set to :email by default and can be changed to :username
5
+ # in config/app.rb
6
+ field login_field
7
+ field :name
8
+
9
+ validate login_field, unique: true, length: 8
10
+ validate :email, email: true
11
+
4
12
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: volt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.4.pre1
4
+ version: 0.9.4.pre2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Stout
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-06-12 00:00:00.000000000 Z
11
+ date: 2015-06-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -409,7 +409,6 @@ files:
409
409
  - lib/volt/cli/new_gem.rb
410
410
  - lib/volt/cli/runner.rb
411
411
  - lib/volt/config.rb
412
- - lib/volt/controllers/actions.rb
413
412
  - lib/volt/controllers/collection_helpers.rb
414
413
  - lib/volt/controllers/http_controller.rb
415
414
  - lib/volt/controllers/model_controller.rb
@@ -576,6 +575,7 @@ files:
576
575
  - lib/volt/utils/event_counter.rb
577
576
  - lib/volt/utils/generic_counting_pool.rb
578
577
  - lib/volt/utils/generic_pool.rb
578
+ - lib/volt/utils/lifecycle_callbacks.rb
579
579
  - lib/volt/utils/local_storage.rb
580
580
  - lib/volt/utils/logging/task_argument_filterer.rb
581
581
  - lib/volt/utils/logging/task_logger.rb
@@ -631,7 +631,6 @@ files:
631
631
  - spec/apps/kitchen_sink/config.ru
632
632
  - spec/apps/kitchen_sink/config/app.rb
633
633
  - spec/apps/kitchen_sink/config/base/index.html
634
- - spec/controllers/actions_spec.rb
635
634
  - spec/controllers/http_controller_spec.rb
636
635
  - spec/controllers/model_controller_spec.rb
637
636
  - spec/controllers/reactive_accessors_spec.rb
@@ -664,6 +663,7 @@ files:
664
663
  - spec/models/buffer_spec.rb
665
664
  - spec/models/dirty_spec.rb
666
665
  - spec/models/field_helpers_spec.rb
666
+ - spec/models/model_helpers/model_helpers_spec.rb
667
667
  - spec/models/model_spec.rb
668
668
  - spec/models/model_state_spec.rb
669
669
  - spec/models/permissions_spec.rb
@@ -726,6 +726,7 @@ files:
726
726
  - spec/utils/ejson_spec.rb
727
727
  - spec/utils/generic_counting_pool_spec.rb
728
728
  - spec/utils/generic_pool_spec.rb
729
+ - spec/utils/lifecycle_callbacks_spec.rb
729
730
  - spec/utils/parsing_spec.rb
730
731
  - spec/utils/promise_extensions_spec.rb
731
732
  - spec/utils/task_argument_filtererer_spec.rb
@@ -877,7 +878,6 @@ test_files:
877
878
  - spec/apps/kitchen_sink/config.ru
878
879
  - spec/apps/kitchen_sink/config/app.rb
879
880
  - spec/apps/kitchen_sink/config/base/index.html
880
- - spec/controllers/actions_spec.rb
881
881
  - spec/controllers/http_controller_spec.rb
882
882
  - spec/controllers/model_controller_spec.rb
883
883
  - spec/controllers/reactive_accessors_spec.rb
@@ -910,6 +910,7 @@ test_files:
910
910
  - spec/models/buffer_spec.rb
911
911
  - spec/models/dirty_spec.rb
912
912
  - spec/models/field_helpers_spec.rb
913
+ - spec/models/model_helpers/model_helpers_spec.rb
913
914
  - spec/models/model_spec.rb
914
915
  - spec/models/model_state_spec.rb
915
916
  - spec/models/permissions_spec.rb
@@ -972,6 +973,7 @@ test_files:
972
973
  - spec/utils/ejson_spec.rb
973
974
  - spec/utils/generic_counting_pool_spec.rb
974
975
  - spec/utils/generic_pool_spec.rb
976
+ - spec/utils/lifecycle_callbacks_spec.rb
975
977
  - spec/utils/parsing_spec.rb
976
978
  - spec/utils/promise_extensions_spec.rb
977
979
  - spec/utils/task_argument_filtererer_spec.rb