How to dynamically define `prop`s on a `T::Struct` in sorbet at runtime? - ruby

I have a struct that is defined as:
# typed: true
require 'sorbet-runtime'
class MyStruct < T::Struct
MyPropType = T.type_alias { T::Hash[Symbol, Class] }
class << self
extend T::Sig
sig { params(props: MyPropType).void }
def register_props(props)
props.each do |prop_name, prop_type|
prop(prop_name, prop_type)
end
end
end
end
Notice how props are defined at runtime.
Then somewhere in my codebase, at startup, I do MyStruct.register_props({ foo: T.untyped, bar: T.nilable(T.untyped) }).
Initializing MyStruct gives error when passing the codebase through typecheck. MyStruct.new(foo: 'foo', bar: Bar.new).
$ ./bin/srb typecheck
/path/to/file.rb:66: Too many arguments provided for method MyStruct#initialize. Expected: 0, got: 1 https://srb.help/7004
How do I define props on T::Struct at runtime without the above typecheck error?

AFAIK T::Structs cannot be defined dynamically (I mean they can but...), since the typechecker needs to statically know which props it's going to have. For this case I think you should use T::InexactStruct. See https://github.com/sorbet/sorbet/blob/master/gems/sorbet-runtime/lib/types/struct.rb
EDIT: Add snippet for future references
# typed: strict
class SomeStruct < T::InexactStruct
extend T::Sig
sig { params(props: T::Array[T.untyped]).void }
def self.register(props)
props.each do |name, type|
prop name, type
end
end
end
SomeStruct.register [[:one, String], [:two, String]]
SomeStruct.new() # This would raise an error on runtime because of missing arguments, but not on static check
SomeStruct.new(one: '', two: '') # works on runtime, no static error
SomeStruct.new(one: '', two: 1) # fails on runtime because of type mismatch, no static error
SomeStruct.new(one: '', two: '', three: '') # fails on runtime because of extra argument, no static error

Related

Resolvers in GraphQL do not seem to get context

I am using resolvers inside query_type:
module Types
class QueryType < Types::BaseObject
...
field :gather_things,
resolver: Resolvers::GatherThings,
null: true do
argument :scope, String, required: false
argument :scope_id, ID, required: false
...
And in my resolvers folder there is a base file:
module Resolvers
class Base < GraphQL::Schema::Resolver
def current_user_id
#current_user_id ||= context[:current_user].id
end
end
end
However, when I try to use current_user_id inside the resolver code, it breaks:
module Resolvers
class GatherThings < Resolvers::Base
...
def things_resolver(scope, scope_id)
if scope.nil?
Thing.from_user(current_user_id)
...
end
end
...
Saying : "undefined method 'id' for nil:NilClass"
Any clue?
I later found out that something was nullifying my session upstream.
This was due to the use of JWTs for session management and mixing them with cookies for OmniAuth.
I have to work out on a solution for using both depending on the intended use.

error_class=NoMethodError error="undefined method `bytesize' Fluentd

I have the below Fluentd plugin code:
require 'avro'
module Fluent
module TextFormatter
class Sample
end
class AvroFormatter < Formatter
Fluent::Plugin.register_formatter('avro', self)
config_param :schema_file, :string, :default => nil
config_param :schema_json, :string, :default => nil
def configure(conf)
super
if not (#schema_json.nil? ^ #schema_file.nil?) then
raise Fluent::ConfigError, 'schema_json or schema_file (but not both) is required'
end
if #schema_json.nil? then
#schema_json = File.read(#schema_file)
end
#schema = Avro::Schema.parse(#schema_json)
end
def format(tag, time, record)
handler = Sample.new()
end
end
end
end
And I need to instance the class "Sample" in the def "Format". The problem is that when I try to do a http POST against Fluentd the below error appears:
failed: error_class=NoMethodError error="undefined method `bytesize'
This error only appears when the class "Sample" is instanced. I'm new with ruby, and I don't know where is the problem. Should I create the class "Sample" in another file?
I think you're getting this error because code, that calls format expects string result, but instead it gets an instance of Sample class. Try to return some string instead.
You can also use this example here: http://docs.fluentd.org/articles/plugin-development#text-formatter-plugins.

ruby: calling a instance method without using instance

I know in ruby, when we call an instance method, we need to firstly instantiate a class object.
But when I see a open sourced code I got confused.
The code is like this:
File Message.rb
require 'json'
module Yora
module Message
def serialize(msg)
JSON.generate(msg)
end
def deserialize(raw, symbolized_key = true)
msg = JSON.parse(raw, create_additions: true)
if symbolized_key
Hash[msg.map { |k, v| [k.to_sym, v] }]
else
msg
end
end
end
end
File. Persistance.rb
require 'fileutils'
require_relative 'message'
module Yora
module Persistence
class SimpleFile
include Message
def initialize(node_id, node_address)
#node_id, #node_address = node_id, node_address
FileUtils.mkdir_p "data/#{node_id}"
#log_path = "data/#{node_id}/log.txt"
#metadata_path = "data/#{node_id}/metadata.txt"
#snapshot_path = "data/#{node_id}/snapshot.txt"
end
def read_metadata
metadata = {
current_term: 0,
voted_for: nil,
cluster: { #node_id => #node_address }
}
if File.exist?(#metadata_path)
metadata = deserialize(File.read(#metadata_path)) #<============
end
$stderr.puts "-- metadata = #{metadata}"
metadata
end
.....
You can see the line I marked with "<==="
It uses deserialize function that been defined in message class.
And from message class we can see that method is a instance method, not class method.
So why can we call it without instantiating anything like this?
thanks
Message ist an module. Your Class SimpleFile includes this module. so the module methods included in your class SimpleFile. that means, all module methods can now be used like as methods from SimpleFile
see http://ruby-doc.org/core-2.2.0/Module.html for more infos about module in ruby. it's a great feature.
It is being called on an instance. In Ruby, if you leave out the explicit receiver of the message send, an implicit receiver of self is assumed. So, deserialize is being called on an instance, namely self.
Note that this exact same phenomenon also occurs in other places in your code, much earlier (in line 1, in fact):
require 'fileutils'
require_relative 'message'
Here, you also have two method calls without an explicit receiver, which means that the implicit receiver is self.

Check if a namedspaced class exists

I'm writing a helper to check if a given class exists.
def safe_constant(constant_sym)
return Object.const_get(constant_sym) if Object.const_defined? constant_sym
end
This works just fine for something like safe_constant(:User)
However, we have a number of objects that are namespaced in modules
safe_constant(:Admin::User) is obviously not a valid symbol. I know I could determine if this exists by doing:
Object.const_get(:Admin).const_get(:User) but before I go writing some kind of split loop drill-down method to get the final constant, is there any standard way to handling this already?
According to Ruby API document, it's possible to get const_defined? and const_get working with namespace string to check and return the class constant. What needs to be done is passing the second argument (inherit) as false, for example:
class User; end
module Admin
class User; end
end
Object.const_defined?('Admin::User', false) # => true
Object.const_get('Admin::User', false) # => Admin::User
PS: const_defined? and const_get do not accept namespaced Symbol and will raise NameError exception, for example:
# namespaced String
Object.const_defined?('Admin::User', false) # => true
# namespaced Symbol
Object.const_defined?(:'Admin::User', false)
# => NameError (wrong constant name Admin::User)
How about something like this?
def safe_constant(constant_sym)
return constant_sym.to_s.constantize if valid_constant? constant_sym.to_s
end
def valid_constant?(const)
const.constantize
return true
rescue NameError
return false
end

How to disable warning for redefining a constant when loading a file

Is there a way to disable warning: already initialized constant when loading particular files?
The solution to your problem depends on what is causing it.
1 - You are changing the value of a constant that was set before somewhere in your code, or are trying to define a constant with the same name as an existant class or module. Solution: don't use constants if you know in advance that the value of the constant will change; don't define constants with the same name as class/modules.
2 - You are in a situation where you want to redefine a constant for good reasons, without getting warnings. There are two options.
First, you could undefine the constant before redefining it (this requires a helper method, because remove_const is a private function):
Object.module_eval do
# Unset a constant without private access.
def self.const_unset(const)
self.instance_eval { remove_const(const) }
end
end
Or, you could just tell the Ruby interpreter to shut up (this suppresses all warnings):
# Runs a block of code without warnings.
def silence_warnings(&block)
warn_level = $VERBOSE
$VERBOSE = nil
result = block.call
$VERBOSE = warn_level
result
end
3 - You are requiring an external library that defines a class/module whose name clashes with a new constant or class/module you are creating. Solution: wrap your code inside a top-level module-namespace to prevent the name clash.
class SomeClass; end
module SomeModule
SomeClass = '...'
end
4 - Same as above, but you absolutely need to define a class with the same name as the gem/library's class. Solution: you can assign the library's class name to a variable, and then clear it for your later use:
require 'clashing_library'
some_class_alias = SomeClass
SomeClass = nil
# You can now define your own class:
class SomeClass; end
# Or your own constant:
SomeClass = 'foo'
Try this :
Kernel::silence_warnings { MY_CONSTANT = 'my value '}
To suppress warnings, use the following code at the top of the script:
$VERBOSE = nil
The accepted answer to this question was helpful. I looked at the Rails source to get the following. Before and after loading the file, I can insert these lines:
# Supress warning messages.
original_verbose, $VERBOSE = $VERBOSE, nil
load(file_in_question)
# Activate warning messages again.
$VERBOSE = original_verbose
Using user2398029's reply the simplest way for me to remove warnings was to add this line:
before { described_class.instance_eval { remove_const(:CONSTANT_NAME) } }

Resources