1. Check of arguments and return value

The most basic use of NeuronCheck is to check whether or not that arguments or return value of the method are expected. This is what is commonly referred to as “type-checking”, but, you can also perform validation of more than type-checking in NeuronCheck. (Such as whether the check whether the value is within the specified range)

require 'neuroncheck'

class Foo
  extend NeuronCheck

  ndecl {
    args String, Numeric, respondable(:each), ['flash', 'none']
    returns :self
  }
  def foo_method(text, rate, targets, type)
    # Main Process...

    return self
  end
end

A ndecl block declares the types of arguments and return values for foo_method. And, at the time of execution to the method, and run the check whether the actually passed arguments and return values are consistent with the declaration.

Before you run the first ndecl, please note that must be kept running extend NeuronCheck within the definition of the class or module.

By declaring, as in the example of the above code, you can do the check when foo_method is called.

  • text is a String
  • rate is a Numeric (a subclass of Numeric also accepted – for example, Integer, Float and Rational)
  • targets can respond to the each method (for example, Array, Hash and Range)
  • type is one of the values of the ‘flash’ or ‘none’
  • this method returns self

In fact within the script, if you pass an invalid argument when calling the foo_method, NeuronCheck will display an error similar to the following.

inst = Foo.new
inst.foo_method('Value', 0.8, 'flash')
#=> No error

inst.foo_method('Value', '0.8', 'flash')
#=> Exception of the following raises
#   script.rb:20:in `<main>': 2nd argument `rate' of `Foo#foo_method' must be Numeric, but was "0.8" (NeuronCheckError)
#             got: "0.8"
#       signature: Foo#foo_method(value:String, rate:Numeric, targets:respondable(:each), type:["flash", "none"]) -> self
#     declared at: script.rb:7:in `block in <class:Foo>'

Perhaps even if you do not use the NeuronCheck, it will error occurs somewhere in the timing in the main processing. But, by using the NeuronCheck, at an earlier stage, descriptive will be error display is performed, it can be expected to become more easy to understand the cause of the error.


By the way, the value that can be used to check, there are also a variety of other than the example of the first code. For example…

args [String, nil]      #=> check that the argument is a string or nil
args [true, false]      #=> check that the argument is true or false
args /ruby/             #=> check that the argument is a string that contains the word "ruby"
args 0..100             #=> check that the argument is a number between 0 and 100

args any                #=> argument can be any value
args except(nil)        #=> argument may be of any value if the non-null

args array_of(String)          #=> Check that the argument is an array that contains only the string as the content
args hash_of(Symbol, Integer)  #=> Check that the argument is a Hash, that has the Symbol keys and the Integer values
                               #   e.g.) {:foo => 1, :bar => 2}

If you want to see all of the available check, please refer to the separate document List of checking values for arguments and return value.

By using the plug-in mechanism of NeuronCheck, it is also possible that the user to add your own keyword (for example, by adding a boolean keyword, such as to allow easy verification of the true / false). Please refer to Add user keywords for more information.


In addition, it is also possible to carry out a check for variable number of arguments or keyword arguments.

class Blog
  extend NeuronCheck

  ndecl {
    args String, Symbol, [true, false]
  }
  def post_article(title, *tags, public: false)
  end
end

blog = Blog.new
blog.post_article("test article 1")                      #=> no error
blog.post_article("test article 1", :food, :technology)  #=> no error
blog.post_article("test article 1", public: true)        #=> no error

blog.post_article("test article 1", 'FOOD')              #=> Error because String was passed to the `tags`
blog.post_article("test article 1", public: 1)           #=> Error because a value except true and false was passed to the `public`

In addition, for initialize method, singleton methods or the accessor definition due to attr_accessor and attr_reader, you can check in the same way.

class Blog
  extend NeuronCheck

  ndecl {
    args String
    returns Blog
  }
  def self.load(path)
  end

  ndecl { val String }
  attr_accessor :title

  ndecl { args String }
  def initialize(name)
  end
end

It should be noted, only when performing the check for attr_accessor and attr_reader, please note that there is a need to use val instead of args. Please refer to the section in the Declaration for the attribute method (attr_ *) for more information.

2. Disabling check

The major features of the NeuronCheck is, is that there is a very very small impact on the production environment performance.
Since it is possible to turn on / off all the check processing at any time, while benefiting from check in the development environment, in a production environment you will be able to achieve maximum performance without the check.

It is based on specification of Eiffel and D language – remove the check processing from executable when release build.

If you want to turn off the check processing, please run the in the code NeuronCheck.disable.

NeuronCheck.disable

After running the NeuronCheck.disable is, without check processing, the influence of the NeuronCheck has on the performance will be almost zero.

require 'neuroncheck'

class Blog
  extend NeuronCheck

  ndecl { args String }
  def initialize(title)
  end
end

blog = Blog.new(100) # => raise an error because you pass a numeric value

NeuronCheck.disable
blog = Blog.new(100) # => Since all of the check process is disabled, it does not result in an error

NeuronCheck.disable You can also call with a block. If this is the case, it will be NeuronCheck invalidation only within the block.

NeuronCheck.disable do
  blog = Blog.new(100) # => Since all of the check process is disabled, it does not result in an error
end

In a real application, a look at the pre-set value, you will need to run the appropriate NeuronCheck.disable.

For example, if the Web apps using Sinatra, by how to write, such as the following, you can deactivate the NeuronCheck only the case of the production environment.

configure :production do
  NeuronCheck.disable
end

3. A variation of syntax

In NeuronCheck, in order to be able to describe as much as possible without stress check processing, it supports number of ways of how to write.

First, the ndecl method, because it is defined an alias named ndeclare, ncheck, nsig and ntypesig, you can use your favorite method name to your liking.

# All the following description is the same effect
ndecl { ... }
ndeclare { ... }
ncheck { ... }
nsig { ... }
ntypesig { ... }

If further Ruby 2.1 or later, use the Refinement function, by executing using NeuronCheckSyntax, you will be able to more concise writing.

For example, in the usually do the description, such as the following…

require 'neuroncheck'

class Converter
  extend NeuronCheck

  ndecl {
    args String, respondable(:each), [Numeric, nil]
    returns String
  }
  def convert(text, keywords, threshold = nil)
    # (main process)
  end
end

When you use the using NeuronCheckSyntax, you can write in this way.

require 'neuroncheck'
using NeuronCheckSyntax

class Converter
  decl String, respondable(:each), [Numeric, nil] => String
  def convert(text, keywords, threshold = nil)
    # (main process)
  end
end

With this feature, you can write multiple arguments and return value check on one line. In addition, extend NeuronCheck in each class will be unnecessary.

For more information on this feature, please refer to the Shorthand syntax with NeuronCheckSyntax.

4. Check the pre-conditions and post-conditions

In NeuronCheck, you can also perform the check of pre-conditions and post-conditions that have been proposed in Contract Programming. When you use this feature, or perform more detailed check for the arguments and return values, it is possible to carry out a check on the state of the instance.

In order to perform a check of the pre-conditions and post-conditions, in the declaration part by ndecl, please describe in the following manner.

ndecl {
  precond do
    ...
  end

  postcond do |ret|
    ...
  end
}

“Pre-condition” is checked before the call of the method, to determine whether the value of the argument and instance variables are matched to the conditions.
“Post-condition” is called after a call to a method has been completed, the value of the return value and instance variables will determine whether or not match the conditions.

Using this pre-and post-conditions, shows actually a code example in which the check in the following.

require 'neuroncheck'

class Foo
  extend NeuronCheck

  def initialize
    @file_loaded = false
  end

  ndecl {
    args String

    # Pre-condition
    precond do
      # Determining whether the number of characters in the argument "name" does not exceed 10. If more than 10 errors
      #  (Because it passed the argument checking, it is guaranteed a String)
      assert{ name.length <= 10 }

      # Determining whether the instance variable "@file_loaded" is true. If false or nil error
      assert{ @file_loaded }
    end

    # Post-condition
    postcond do |ret|
      # If the check of the return value is required, write here
    end
  }
  def foo_method(name)
    # Main process
  end
end

inst1 = Foo.new
inst1.foo_method('therubyracer')  # An error because the argument name is longer than 10 characters
inst1.foo_method('rubyracer')     # While argument name has subsided within 10 characters, @file_loaded at this point because of the false, still results in an error

It should be noted that, in this condition check, usually Please note that you can not run the instance method or instance accessor. It also ignored the assignment to instance variable.

  precond do
    self.method1            # Instance method can not be executed (raises NoMethodError)

    @file_loaded = true     # Although not an error, not actually assigned a value to an instance variable
  end

NeuronCheck The prohibits the execution of an instance method or accessor, because there may be side effects is not a self-evident to the instance when you run – By executing the get_foo methods, such as the value of an instance variable @foo is initialized. This means that there is a possibility that the behavior is different when you have to and off when you turn on the NeuronCheck.

If you really want to call the method or accessor with the instance, please use the allow_instance_method option.

precond(allow_instance_method: true) do
  method1            # No error!
end

However, if you want to call the accessor or instance method in the pre-conditions and post-conditions, please check carefully that the side effects do not occur.

It should be noted that, if you want to abort the check processing in the middle of the pre-conditions and post-conditions, please use the return and break rather than next.

precond do
  break # raises LocalJumpError (Limit of the Ruby language)

  next  # OK
end

5. Declaration for the attribute method (attr_*)

In NeuronCheck, about the attributes get / set methods, you can do a check processing.

However, in this case, unlike the usual method definition, please use val (or alias must_be, value) instead of args and returns.

class Foo
  extend NeuronCheck

  ndecl { val String }
  attr_accessor :caption
end

foo_inst = Foo.new
foo_inst = 'CAPTION'  #=> OK
foo_inst = 1 #=> raises NeuronCheckError
# All the following description is valid
ndecl { val String }
ndecl { value String }
ndecl { must_be String }

Also in this case, for more than one attribute, it is also possible to apply the check at a time.

class Foo
  ndecl { val String }
  attr_accessor :caption1, :caption2  # => To both of caption1 and caption2 String attriutes
end

With NeuronCheckSyntax, you can write in the following manner.

using NeuronCheckSyntax

class Foo
  ndecl String
  attr_accessor :caption1, :caption2
end

6. To perform a check of NeuronCheck other than the method definition (without pre-declaration)

NeuronCheck is usually is a library to set the check for the method definition, you might want to check the processing of which does not sometimes have a method definition. For example, when you want to check conditions in the Rake tasks, such as when you want to check at the time of routing processing If you’re using Sinatra.

For these cases, NeuronCheck provides methods, check and match? For carrying out a single check with no declaration. In the same mechanism as the check of the arguments and return values, you can one value to check whether the correct value.

NeuronCheck.check(str){ String }     # The value of str is not nothing if string. If otherwise raises an error

NeuronCheck.match?(str){ String }    # Same as above, but without raising an error, return a true or false instead

7. Operation at the time of inheritance

If you inherit the class that defines the NeuronCheck, what happens is the check that method?

For this, NeuronCheck will work in accordance with the following simple principle – All parent class check is performed. Even if you define a pre-condition in the subclass, the pre-condition of the parent class is still working.

For example, make the following inheritance…

require 'neuroncheck'

class Class1
  extend NeuronCheck

  ndecl {
    args String # argument check A

    precond do
      # pre-condition check A
    end

    postcond do |ret|
      # post-condition check A
    end
  }

  def foo(str1)
    # (main process A)
  end
end

class Class2 < Class1
  extend NeuronCheck

  ndecl {
    args String, String # argument check B

    precond do
      # pre-condition check B
    end

    postcond do |ret|
      # post-condition check B
    end
  }

  def foo(str1, str2)
    super(str1)

    # (main process B)
  end
end

In this state, you may have to call the foo method on an instance of Class2.

inst = Class2.new
inst.foo("a", "b")

In this case, the order in the following processing will be performed.

  1. Declared in the foo method of Class2, argument check B and pre-condition check B is executed (Error if the incorrect value)
  2. Class2 of foo method is executed, calling the super(str1)
  3. Declared in the foo method of Class1, argument check A and pre-condition check A is executed (Error if the incorrect value)
  4. Main processing 1 is executed in the Class1 foo method
  5. For the return value, declared in the foo method of Class1, post-condition check A is executed (Error if the incorrect value)
  6. Main processing 2 is executed in the Class2 foo method
  7. For the return value, declared in the foo method of Class2, post-condition check B is executed (Error if the incorrect value)

This behavior is different behavior from the Eiffel and D language is an ancestor of contract programming.