Skip to content

Commit 7eba37b

Browse files
committed
fixed regression and other issues raised by #173
1 parent ba02475 commit 7eba37b

File tree

4 files changed

+42
-12
lines changed

4 files changed

+42
-12
lines changed

docs/dsl-isomorphic/hyper-operation.md

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,32 @@
22

33
**Work in progress - ALPHA (docs and code)**
44

5-
Operations are the engine rooms of Hyperstack; they orchestrate the interactions between Components, external services, Models, and Stores. Operations provide a tidy place to keep your business logic.
5+
Operations are Hyperstack's implementation of Service Object which is...
66

7-
Operations receive parameters and execute a series of steps. They have a simple structure which is not dissimilar to a Component:
7+
> "A class that performs an action" [A simple explanation of Service Objects for Ruby on Rails](https://medium.freecodecamp.org/service-objects-explained-simply-for-ruby-on-rails-5-a8cc42a5441f)
8+
9+
Why do we need Service Objects? Because in any real world system you have logic that does belong in models or stores because it effects multiple models or stores, and it does not belong in components because the logic of the task is independent of the specific user interface design.
10+
11+
There are also those boundary areas between gathering and processing external data and getting into or out of our stores and models. You don't want that kind of logic in your model or store, so where does it go? Into an operation.
12+
13+
Simply put an operation is like a large standalone method that has no internal state of its own. You run an operation, it does it thing, and it returns an answer.
14+
15+
Any state that an operation needs to retrieve or save is stored somewhere else: in a model, a store, or even in a remote API. Once the operation completes, it has no memory of its own.
16+
17+
18+
Because this an operation's full time mission the Hyperstack Operation base class is structured to make writing this kind of code easy.
19+
20+
An Operation may take parameters (params) just like any other method.
21+
An Operation may validate the parameters
22+
An Operation then executes a number of steps
23+
The value of the final step is returned
24+
25+
Hyperstack Operations often involve asynchronous methods such as HTTP requests, and so Operations always return promises
26+
27+
28+
; they orchestrate the interactions between Components, external services, Models, and Stores. Operations provide a tidy place to keep your business logic.
29+
30+
Operations receive parameters, va and execute a series of steps They have a simple structure which is not dissimilar to a Component:
831

932
```ruby
1033
class SimpleOperation < Hyperstack::Operation

ruby/hyper-model/lib/reactive_record/active_record/reactive_record/isomorphic_base.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ def self.gather_records(records_to_process, force, record_being_saved)
243243
add_new_association.call record, attribute, value.backing_record
244244
end
245245
end
246-
elsif aggregation = record.model.reflect_on_aggregation(attribute) and (aggregation.klass < ActiveRecord::Base)
246+
elsif (aggregation = record.model.reflect_on_aggregation(attribute)) && (aggregation.klass < ActiveRecord::Base)
247247
add_new_association.call record, attribute, value.backing_record unless value.nil?
248248
elsif aggregation
249249
new_value = aggregation.serialize(value)
@@ -483,8 +483,8 @@ def self.save_records(models, associations, acting_user, validate, save)
483483
next true if record.frozen? # skip (but process later) frozen records
484484
next true if dont_save_list.include?(record) # skip if the record is on the don't save list
485485
next true if record.changed.include?(record.class.primary_key) # happens on an aggregate
486-
next true if record.persisted? # record may be have been saved as result of has_one assignment
487-
next false if record.id && !record.changed? # throw out any existing records with no changes
486+
#next true if record.persisted? # record may be have been saved as result of has_one assignment
487+
next record.persisted? if record.id && !record.changed? # throw out any existing records with no changes
488488
# if we get to here save the record and return true to keep it
489489
op = new_models.include?(record) ? :create_permitted? : :update_permitted?
490490
record.check_permission_with_acting_user(acting_user, op).save(validate: false) || true
@@ -500,6 +500,7 @@ def self.save_records(models, associations, acting_user, validate, save)
500500
# the all the error messages during a save so we can dump them to the server log.
501501

502502
all_messages = []
503+
attributes = nil
503504

504505
saved_models = reactive_records.collect do |reactive_record_id, model|
505506
messages = model.errors.messages if validate && !model.valid?
@@ -522,7 +523,6 @@ def self.save_records(models, associations, acting_user, validate, save)
522523
end
523524
raise 'HyperModel saving records failed!'
524525
end
525-
526526
end
527527

528528
{ success: true, saved_models: saved_models }

ruby/hyper-model/lib/reactive_record/active_record/reactive_record/setters.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,9 @@ def change_status_and_notify_helper(attr, changed)
120120
# || data_loading? added so that model.new can be wrapped in a ReactiveRecord.load_data
121121
if !changed || data_loading?
122122
changed_attributes.delete(attr)
123-
elsif !changed_attributes.include?(attr)
124-
changed_attributes << attr
123+
else
124+
changed_attributes << attr if !changed_attributes.include?(attr)
125+
synced_attributes.delete(attr)
125126
end
126127
yield @attributes.key?(attr), @attributes[attr]
127128
return unless empty_before != changed_attributes.empty?

ruby/hyper-model/spec/batch1/misc/validate_spec.rb

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,22 @@
1010
# before each test
1111
stub_const 'TestApplication', Class.new
1212
stub_const 'TestApplicationPolicy', Class.new
13+
User.do_not_synchronize
14+
TestModel.do_not_synchronize
1315
TestApplicationPolicy.class_eval do
14-
#always_allow_connection TURN OFF BROADCAST SO TESTS DON"T EFFECT EACH OTHER
16+
always_allow_connection
1517
regulate_all_broadcasts { |policy| policy.send_all }
1618
allow_change(to: :all, on: [:create, :update, :destroy]) { true }
1719
end
18-
#size_window(:large, :landscape)
1920
User.validates :last_name, exclusion: { in: %w[f**k], message: 'no swear words allowed' }
2021
TestModel.validates_presence_of :child_models
21-
client_option raise_on_js_errors: :off
22+
client_option raise_on_js_errors: :off # TURN OFF BROADCAST SO TESTS DON"T EFFECT EACH OTHER
23+
end
24+
25+
after(:step) do
26+
# Turn broadcasting back on
27+
TestModel.instance_variable_set :@do_not_synchronize, false
28+
User.instance_variable_set :@do_not_synchronize, false
2229
end
2330

2431
it "can validate the presence of an association" do
@@ -72,7 +79,6 @@ class << self
7279
user = User.new(last_name: 'f**k')
7380
user.save(validate: true).then { |result| user.valid?}
7481
end.to be_falsy
75-
7682
end
7783

7884
it "the valid? method reacts to the model being saved" do

0 commit comments

Comments
 (0)