expose_as_operations method
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 2/5
- Thời gian dự kiến
- 1-3 giờ
- Mức phù hợp với người mới
- 68/100
Hướng nghiên cứu
Bắt đầu với triển khai được đề xuất trong hyperstack/models/expose_as_operations.rb và việc đưa ApplicationRecord vào như được mô tả trong issue. Kiểm tra các ví dụ phía client và server cho expose_as_operation và expose_as_operations, sau đó xác minh rằng các lệnh gọi được expose trả về một promise, thực thi security block tùy chọn và từ chối các record chưa được lưu hoặc không tồn tại.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
This is a nice standalone module that adds two class level methods to ActiveRecord Models, that will expose server side methods to the client as operations.
Unlike server_methods, methods exposed using expose_as_operation run as server operations. The method is asynchronously executed on the server, and the client gets a promise that resolves when the method completes.
This is much more useful than server_methods if the you are trying to trigger some server side operation in an event handler. The downside is that unlike server_methods there will be no automatic client re-render when the operation returns.
So the rule is: If its returning data for the user - use a server_method. If its triggering an operation on the server then expose it as an operation.
Add the ExposeAsOperation module to hyperstack/models, and then include the module in ApplicationRecord:
# hyperstack/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
...
include ExposeAsOperations
...
end
Now to expose a method as an operation say this in your model defintion:
# hyperstack/models/my_model.rb
class MyModel < ApplicationRecord
def foo(value)
... do something on the server ...
end
...
expose_as_operation(:foo)
end
somewhere on the client (usually in an event handler) you can say
my_model_instance.foo(12)
Note that foo returns a promise so you can add a .then clause to do anything you need to when the operation completes:
my_model_instance.foo(12).then { |ret_value| mutate @foo_ret_value = ret_value }
The expose_as_operation method can take a block that is used to protect the method from illegal access. Like other policy regulations, the block is executed with self equal to the model instance, and the acting_user attribute of self set to the current acting user, so you can say for example:
expose_as_operation(:admins_only) { acting_user.admin? }
Returning a falsy value, or raising an error will abort the method call, and return an access violation error to the client.
You can expose multiple methods at once:
expose_as_operations(:foo, :bar, :baz) { ... common regulation ... }
Typically the exposed methods will either be in a separate file visible on the server, or will be surrounded by an
unless RUBY_ENGINE == 'opal' guard.
class MyModel < ApplicationRecord
include MyModelServerDefinitions unless RUBY_ENGINE == 'opal' # defines meth1, and meth2 plus others...
expose_as_operation(:meth1, :meth2)
end
We may eventually include this code in Hyperstack, but for now its easy enough to add the file to your models directory:
# hyperstack/models/expose_as_operations.rb
# frozen_string_literal: true
# ExposeAsOperations adds the following methods to ActiveRecord models
# expose_as_operations(list of method names) { security block }
# each of the method names will now be callable from the client
# as an ServerOp. In otherwords on the client the method will
# return a promise that will resolve (or reject) when the method
# completes execution on the server.
#
# The optional security block will be called before the method is invoked
# and is passed the method name, and the arguments. Within the block
# self is the active record instance, and will have the current value
# of acting_user. If the security block returns a falsy value or
# raises an error, the operation will not be executed, and
# the operation will fail with a Hyperstack::AccessViolation error.
#
# For example
# expose_as_operations(:set_brightness, :toggle_switch) do
# users.includes? acting_user
# end
#
# For readability you can also use expose_as_operation for a single method.
#
# Caveat: The record must be saved. I.e. you cannot execute the methods
# from the client on unsaved records.
#
module ExposeAsOperations
def self.included(base)
base.extend ClassMethods
end
# each class calling exposing_as_operation will create its subclass
# of the Base operation. Each subclass of Base has its own internal
# list of exposed methods, with an associated security block.
# The Base operation simply validates the operation using the security
# block, and then calls the method on the parent class.
class Base < Hyperstack::ServerOp
param :acting_user, nils: true
param :id
param :method
param :args
unless RUBY_ENGINE == 'opal'
# Abort with a nice message unless we can find the record using the id.
validate :insure_record_exists
# Call the security block to validate the method is callable,
validate :with_security_block
# and convert any failures above to AccessViolations.
failed { raise Hyperstack::AccessViolation }
# If we get here then we have a valid record, and we are allowed to call
# the method. Any errors in the method will get sent back to the client.
step { @record.send(params.method, *params.args) }
def insure_record_exists
@record =
self.class.parent.find_by(self.class.parent.primary_key => params.id)
return true if @record
add_error(
:id, :not_found, "#{self.class.parent} id: #{params.id} not found."
)
# abort so we skip the conversion of failures to access violations.
abort!
end
def with_security_block
@record.acting_user = params.acting_user
@record.instance_exec(
params.method.to_sym,
*params.args,
&self.class.security_blocks[params.method.to_sym]
)
end
# list of security blocks indexed by the method name
def self.security_blocks
@security_blocks ||= {}
end
end
end
# define the two class methods
module ClassMethods
def expose_as_operations(*methods, &security_block)
methods.each { |method| add_security_block(method, security_block) }
end
alias expose_as_operation expose_as_operations
private
# the runner method insures that each class that uses expose_as_operations
# has its own subclass of the Base operation, which we call MethodRunner
def runner
return self::MethodRunner if defined? self::MethodRunner
const_set 'MethodRunner', Class.new(Base)
end
if RUBY_ENGINE == 'opal'
# on the client we dont really add a security block, but instead
# just make sure that the MethodRunner API is defined and then
# define the method. The method first makes sure that the id of
# model is loaded, and then calls the MethodRunner's run method
def add_security_block(method, _security_block)
runner # insure runner is defined
define_method(method) do |*args|
Hyperstack::Model.load { id }.then do |id|
self.class::MethodRunner.run(id: id, method: method, args: args)
end
end
end
else
# on the server we add the security block for the method. If no
# security block is provided we create a proc that will return true.
def add_security_block(method, security_block)
runner.security_blocks[method] = security_block || ->(*) { true }
end
end
end
end
- Ngôn ngữ chính
- JavaScript
- Star
- 538
- Fork
- 41
- Chỉ số merge pull request
- Không có pull request nào được merge trong 30 ngày
Hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của hyperstack-org/hyperstack
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
hyperstack-org/hyperstack#267 ·
-
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 25/100
hyperstack-org/hyperstack#457 ·
-
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 35/100
hyperstack-org/hyperstack#456 ·
-
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 15/100
hyperstack-org/hyperstack#455 · 2 bình luận ·
-
won't run with latest puma Đang mở
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 35/100
hyperstack-org/hyperstack#454 · 1 bình luận ·
Tất cả issue của hyperstack-org/hyperstack
Issue tương tự
-
documentation
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 88/100
githubnext/gh-aw-workshop#3692 ·
-
agent/guide documentation hive/hosted-available-lke648397-260827-5n31
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 90/100
-
Add: BuyPass TV Đang mởchannels:add check:passed
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
-
S: triage
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 85/100
-
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 78/100