blockbuilder 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +19 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +168 -0
- data/Rakefile +1 -0
- data/blockbuilder.gemspec +19 -0
- data/lib/block_builder.rb +14 -0
- data/lib/block_builder/attribute.rb +46 -0
- data/lib/block_builder/builder.rb +69 -0
- data/lib/block_builder/version.rb +3 -0
- data/test/Gemfile +2 -0
- data/test/testit.rb +101 -0
- metadata +62 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Christopher Thornton
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,168 @@
|
|
1
|
+
BlockBuilder
|
2
|
+
============
|
3
|
+
Make callbacks easily and build blocks with ease! BlockBuilder is intended to be used with more heavy weight method calls that might require intermediate callbacks. It's almost like creating classes on the fly!
|
4
|
+
|
5
|
+
## Usage
|
6
|
+
Some code samples to get you started:
|
7
|
+
|
8
|
+
### File Download Example
|
9
|
+
Downloading a file probably doesn't merit the block builder, but it demonstrates the concept of callbacks.
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
def download_file(url, &block)
|
13
|
+
|
14
|
+
# Configure your block here, along with defaults
|
15
|
+
builder = BlockBuilder.build block do
|
16
|
+
|
17
|
+
# Set optional default variables!
|
18
|
+
set :verbose, true
|
19
|
+
|
20
|
+
on_fail do |http_code, body|
|
21
|
+
puts "Oh no! HTTP download failed! Code: #{http_code}"
|
22
|
+
puts "Downloaded body: #{body}" if verbose
|
23
|
+
end
|
24
|
+
|
25
|
+
on_success do |body|
|
26
|
+
puts "Downloaded file Successfully!"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Now execute your blocks! Note you don't have to explicitly
|
31
|
+
# define any of your callbacks.
|
32
|
+
result = download_some_file_from_internet(url)
|
33
|
+
if result.http_code == 200
|
34
|
+
builder.on_success.call result.body
|
35
|
+
else
|
36
|
+
builder.on_fail.call result.http_code, result.body
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
# Now you can call the method above and change the defaults!
|
43
|
+
download_file "example.com/exists.txt" do
|
44
|
+
on_success do |body|
|
45
|
+
File.open('some/file.txt', 'w') {|f| f.write body }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Only outputs "Oh no! HTTP download failed! Code: 404"
|
50
|
+
download_file "example.com/doesnotexist.txt" do
|
51
|
+
set :verbose, false
|
52
|
+
end
|
53
|
+
```
|
54
|
+
|
55
|
+
### Callbacks and Remote Data
|
56
|
+
Sometimes you might connect to another service or API that contains critical information. And perhaps you need to query multiple times with this API to get the data you need. Callbacks can help with this!
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
As a convention, you should always document a block builder method and let consumers of
|
60
|
+
# this method know how to use it!
|
61
|
+
# @example
|
62
|
+
# # Here is a code sample on how to use this method, etc
|
63
|
+
def create_remote_user(username, password, &block)
|
64
|
+
builder = BlockBuilder.build block do
|
65
|
+
set :logger, Logger.new
|
66
|
+
|
67
|
+
# You can force the creation of a callback using the abstract operator. By default,
|
68
|
+
# without the abstract modifier, calling an undefined callback will simply do nothing
|
69
|
+
abstract :on_user_created
|
70
|
+
end
|
71
|
+
|
72
|
+
# If any callbacks label as 'abstract' aren't defined, then code execution will
|
73
|
+
# never reach this line.
|
74
|
+
|
75
|
+
# Here, you may first want to create the user and create a callback. This callback
|
76
|
+
# can then be used to quickly save an entry in the database before other operations
|
77
|
+
# are executed. This is useful, for example, if the server crashes during the middle
|
78
|
+
# of the request or there is a bug later in the method call. This way, at least you
|
79
|
+
# have the user information saved in your local database and you can retrieve the
|
80
|
+
# user information later.
|
81
|
+
result = api.create_user username, password
|
82
|
+
builder.on_user_created.call(result)
|
83
|
+
|
84
|
+
# Now we can keep on executing methods and have somewhere else take care of it. Using
|
85
|
+
# the set logger variable, we can allow someone to externally change the logger used.
|
86
|
+
#
|
87
|
+
# Now what if the connection timed out here? Or the server crashed? That's the point
|
88
|
+
# of the earlier callback! It allows you to save partial data without interfering
|
89
|
+
# with the internal logic of this method!
|
90
|
+
start = Time.now
|
91
|
+
result2 = api.some_very_time_intensive_call_involving(username)
|
92
|
+
builder.logger.info "Executed time intensive call in #{Time.now - start} sec!"
|
93
|
+
builder.on_finish_lengthy_task.call(result2)
|
94
|
+
|
95
|
+
# Another fun use for this: collecting statistics! It might be useful to collect a
|
96
|
+
# bunch of statistics for a particular task. However, not every consumer of your
|
97
|
+
# method will care for statistics. Just for fun, let's only calculate some
|
98
|
+
# statistics about this method call only if they care to calculate stats!
|
99
|
+
#
|
100
|
+
# We use 'builder.defined?' here because doing
|
101
|
+
#
|
102
|
+
# builder.generate_statistics.call(some_lengthy_statistic_compilation)
|
103
|
+
#
|
104
|
+
# will compile statistics, regardless of whether 'generate_statistics' is defined.
|
105
|
+
if builder.defined? :generate_statistics
|
106
|
+
stats = some_lengthy_statistic_compilation
|
107
|
+
builder.generate_statistics.call(stats)
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
# Now you can add any more callbacks as you see fit! And don't forget, you can still
|
112
|
+
# return some data from this method call if you want!
|
113
|
+
end
|
114
|
+
```
|
115
|
+
|
116
|
+
Now you can use this method in multiple places. Here, assume this is where the code is primarily used:
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
create_remote_user 'user', 'password' do
|
120
|
+
|
121
|
+
# This easily lets us use our Rails logger
|
122
|
+
set :logger, Rails.logger
|
123
|
+
|
124
|
+
# Remember, our abstract callback was declared, so we must define it here. In this
|
125
|
+
# case, assume we have a Ruby on Rails model that deals with saving saved data
|
126
|
+
on_user_created do |result|
|
127
|
+
u = RemoteUser.new(result)
|
128
|
+
u.some_misc_operations
|
129
|
+
u.save!
|
130
|
+
end
|
131
|
+
end
|
132
|
+
```
|
133
|
+
|
134
|
+
Finally, assume this is some benchmark suite, or something else:
|
135
|
+
|
136
|
+
```ruby
|
137
|
+
# Assume this is some benchmark suite or testing file
|
138
|
+
create_remote_user 'user', 'password' do
|
139
|
+
on_user_created do |result|
|
140
|
+
# .. Implement here
|
141
|
+
end
|
142
|
+
|
143
|
+
# In this case, we might enjoy statistics
|
144
|
+
generate_statistics do |stats|
|
145
|
+
puts "Statistics for this operation:"
|
146
|
+
puts " * Total execution time: #{stats.exec_time}"
|
147
|
+
puts "..."
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
151
|
+
```
|
152
|
+
|
153
|
+
|
154
|
+
|
155
|
+
## Installation
|
156
|
+
|
157
|
+
Add this line to your application's Gemfile:
|
158
|
+
|
159
|
+
gem 'blockbuilder'
|
160
|
+
|
161
|
+
And then execute:
|
162
|
+
|
163
|
+
$ bundle
|
164
|
+
|
165
|
+
Or install it yourself as:
|
166
|
+
|
167
|
+
$ gem install blockbuilder
|
168
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'block_builder/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "blockbuilder"
|
8
|
+
gem.version = BlockBuilder::VERSION
|
9
|
+
gem.authors = ["Christopher Thornton"]
|
10
|
+
gem.email = ["rmdirbin@gmail.com"]
|
11
|
+
gem.description = %q{Make callbacks easily and build blocks with ease! BlockBuilder is intended to be used with more heavy weight method calls that might require intermediate callbacks. It's almost like creating classes on the fly!}
|
12
|
+
gem.summary = %q{Create complex function callbacks with ease!}
|
13
|
+
gem.homepage = "https://github.com/cgthornt/BlockBuilder"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require "block_builder/version"
|
2
|
+
require "block_builder/builder"
|
3
|
+
require "block_builder/attribute"
|
4
|
+
|
5
|
+
module BlockBuilder
|
6
|
+
|
7
|
+
class << self
|
8
|
+
|
9
|
+
def build(target_block, *options, &default_block)
|
10
|
+
return BlockBuilder::Builder.new(target_block, *options, &default_block)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module BlockBuilder
|
2
|
+
class Attribute
|
3
|
+
|
4
|
+
attr_accessor :name, :block, :args, :parent_builder
|
5
|
+
|
6
|
+
def initialize(parent_builder, name, args = nil, block = nil)
|
7
|
+
@name = name
|
8
|
+
@args = args || []
|
9
|
+
@block = block
|
10
|
+
@parent_builder = parent_builder
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
#def set_block(some_block)
|
15
|
+
# block = some_block
|
16
|
+
# # builder._build(some_block) if some_block.is_a?(Proc)
|
17
|
+
#end
|
18
|
+
|
19
|
+
#def builder
|
20
|
+
# @builder = BlockBuilder::Builder.new(block) if @builder.nil?
|
21
|
+
# return @builder
|
22
|
+
#end
|
23
|
+
|
24
|
+
def call(*args)
|
25
|
+
block.call(*args) if block?
|
26
|
+
end
|
27
|
+
|
28
|
+
def block?
|
29
|
+
block.is_a?(Proc)
|
30
|
+
end
|
31
|
+
|
32
|
+
def blank?
|
33
|
+
args.empty? and !block?
|
34
|
+
end
|
35
|
+
|
36
|
+
def val; args.first; end
|
37
|
+
def value; val; end
|
38
|
+
def to_s; val; end
|
39
|
+
|
40
|
+
def vals; args; end
|
41
|
+
def values; vals; end
|
42
|
+
|
43
|
+
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module BlockBuilder
|
2
|
+
class Builder
|
3
|
+
|
4
|
+
attr_reader :_target_block, :_symbol_table, :_options, :_config_table, :_abstract_list
|
5
|
+
|
6
|
+
|
7
|
+
def initialize(target_block, *args, &default_block)
|
8
|
+
@_target_block = target_block
|
9
|
+
options = {}
|
10
|
+
options = args.pop if args.last.is_a?(Hash)
|
11
|
+
args.each{|a| options[a] = true }
|
12
|
+
@_options = options
|
13
|
+
@_symbol_table = Hash.new
|
14
|
+
@_config_table = Hash.new
|
15
|
+
@_abstract_list = Array.new
|
16
|
+
|
17
|
+
# Default block, target block!
|
18
|
+
_build(default_block)
|
19
|
+
_build(target_block)
|
20
|
+
|
21
|
+
# Now call them out on abstractness!
|
22
|
+
_abstract_list.each do |method|
|
23
|
+
raise ArgumentError, "Abstract callback '#{method}' must be defined" unless _symbol_table.key?(method)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
def _build(block)
|
29
|
+
instance_eval(&block) if block.is_a?(Proc)
|
30
|
+
end
|
31
|
+
|
32
|
+
def set(key, val)
|
33
|
+
_config_table[key] = val
|
34
|
+
end
|
35
|
+
|
36
|
+
# Define methods as abstract, so they must be implemented
|
37
|
+
def abstract(*methods)
|
38
|
+
@_abstract_list = _abstract_list | methods
|
39
|
+
end
|
40
|
+
|
41
|
+
def method_missing(method, *args, &block)
|
42
|
+
if _config_table.key?(method) and args.empty? and !block_given?
|
43
|
+
return _config_table[method]
|
44
|
+
end
|
45
|
+
|
46
|
+
method = method.to_s.chomp('=').to_sym if method.to_s[-1] == "="
|
47
|
+
|
48
|
+
attribute = _symbol_table[method]
|
49
|
+
if attribute.nil?
|
50
|
+
attribute = Attribute.new(self, method)
|
51
|
+
_symbol_table[method] = attribute
|
52
|
+
end
|
53
|
+
|
54
|
+
# Read attribute if no params
|
55
|
+
return attribute if args.empty? and !block_given?
|
56
|
+
|
57
|
+
attribute.args = args
|
58
|
+
attribute.block = block if block_given?
|
59
|
+
|
60
|
+
return attribute
|
61
|
+
end
|
62
|
+
|
63
|
+
def defined?(method_call)
|
64
|
+
_symbol_table.key?(method_call)
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
data/test/Gemfile
ADDED
data/test/testit.rb
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'block_builder'
|
4
|
+
|
5
|
+
|
6
|
+
# As a convention, you should always document a block builder method and let consumers of
|
7
|
+
# this method know how to use it!
|
8
|
+
# @example
|
9
|
+
# # Here is a code sample on how to use this method, etc
|
10
|
+
def create_remote_user(username, password, &block)
|
11
|
+
builder = BlockBuilder.build block do
|
12
|
+
set :logger, Logger.new
|
13
|
+
|
14
|
+
# You can force the creation of a callback using the abstract operator. By default,
|
15
|
+
# without the abstract modifier, calling an undefined callback will simply do nothing
|
16
|
+
abstract :on_user_created
|
17
|
+
end
|
18
|
+
|
19
|
+
# If any callbacks label as 'abstract' aren't defined, then code execution will
|
20
|
+
# never reach this line.
|
21
|
+
|
22
|
+
# Here, you may first want to create the user and create a callback. This callback
|
23
|
+
# can then be used to quickly save an entry in the database before other operations
|
24
|
+
# are executed. This is useful, for example, if the server crashes during the middle
|
25
|
+
# of the request or there is a bug later in the method call. This way, at least you
|
26
|
+
# have the user information saved in your local database and you can retrieve the
|
27
|
+
# user information later.
|
28
|
+
result = api.create_user username, password
|
29
|
+
builder.on_user_created(result)
|
30
|
+
|
31
|
+
# Now we can keep on executing methods and have somewhere else take care of it. Using
|
32
|
+
# the set logger variable, we can allow someone to externally change the logger used.
|
33
|
+
#
|
34
|
+
# Now what if the connection timed out here? Or the server crashed? That's the point
|
35
|
+
# of the earlier callback! It allows you to save partial data without interfering
|
36
|
+
# with the internal logic of this method!
|
37
|
+
start = Time.now
|
38
|
+
result2 = api.some_very_time_intensive_call_involving(username)
|
39
|
+
builder.logger.info "Executed time intensive call in #{Time.now - start} sec!"
|
40
|
+
builder.on_finish_lengthy_task.call(result2)
|
41
|
+
|
42
|
+
# Another fun use for this: collecting statistics! It might be useful to collect a
|
43
|
+
# bunch of statistics for a particular task. However, not every consumer of your
|
44
|
+
# method will care for statistics. Just for fun, let's only calculate some
|
45
|
+
# statistics about this method call only if they care to calculate stats!
|
46
|
+
#
|
47
|
+
# We use 'builder.defined?' here because doing
|
48
|
+
#
|
49
|
+
# builder.generate_statistics.call(some_lengthy_statistic_compilation)
|
50
|
+
#
|
51
|
+
# will compile statistics, regardless of whether 'generate_statistics' is defined.
|
52
|
+
if builder.defined? :generate_statistics
|
53
|
+
stats = some_lengthy_statistic_compilation
|
54
|
+
builder.generate_statistics.call(stats)
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
# Now you can add any more callbacks as you see fit! And don't forget, you can still
|
59
|
+
# return some data from this method call if you want!
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
create_remote_user 'user', 'password' do
|
64
|
+
|
65
|
+
# This easily lets us use our Rails logger
|
66
|
+
set :logger, Rails.logger
|
67
|
+
|
68
|
+
# Remember, our abstract callback was declared, so we must define it here. In this
|
69
|
+
# case, assume we have a Ruby on Rails model that deals with saving saved data
|
70
|
+
on_user_created do |result|
|
71
|
+
u = RemoteUser.new(result)
|
72
|
+
u.some_misc_operations
|
73
|
+
u.save!
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
# Assume this is some benchmark suite or testing file
|
79
|
+
create_remote_user 'user', 'password' do
|
80
|
+
on_user_created do |result|
|
81
|
+
# .. Implement here
|
82
|
+
end
|
83
|
+
|
84
|
+
# In this case, we might enjoy statistics
|
85
|
+
generate_statistics do |stats|
|
86
|
+
puts "Statistics for this operation:"
|
87
|
+
puts " * Total execution time: #{stats.exec_time}"
|
88
|
+
puts "..."
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
|
95
|
+
puts "Testing!"
|
96
|
+
def test(&block)
|
97
|
+
builder = BlockBuilder.build block do
|
98
|
+
abstract :my_method
|
99
|
+
end
|
100
|
+
builder.my_method.call
|
101
|
+
end
|
metadata
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: blockbuilder
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Christopher Thornton
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-03-23 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Make callbacks easily and build blocks with ease! BlockBuilder is intended
|
15
|
+
to be used with more heavy weight method calls that might require intermediate callbacks.
|
16
|
+
It's almost like creating classes on the fly!
|
17
|
+
email:
|
18
|
+
- rmdirbin@gmail.com
|
19
|
+
executables: []
|
20
|
+
extensions: []
|
21
|
+
extra_rdoc_files: []
|
22
|
+
files:
|
23
|
+
- .gitignore
|
24
|
+
- Gemfile
|
25
|
+
- LICENSE.txt
|
26
|
+
- README.md
|
27
|
+
- Rakefile
|
28
|
+
- blockbuilder.gemspec
|
29
|
+
- lib/block_builder.rb
|
30
|
+
- lib/block_builder/attribute.rb
|
31
|
+
- lib/block_builder/builder.rb
|
32
|
+
- lib/block_builder/version.rb
|
33
|
+
- test/Gemfile
|
34
|
+
- test/testit.rb
|
35
|
+
homepage: https://github.com/cgthornt/BlockBuilder
|
36
|
+
licenses: []
|
37
|
+
post_install_message:
|
38
|
+
rdoc_options: []
|
39
|
+
require_paths:
|
40
|
+
- lib
|
41
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ! '>='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
48
|
+
none: false
|
49
|
+
requirements:
|
50
|
+
- - ! '>='
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '0'
|
53
|
+
requirements: []
|
54
|
+
rubyforge_project:
|
55
|
+
rubygems_version: 1.8.24
|
56
|
+
signing_key:
|
57
|
+
specification_version: 3
|
58
|
+
summary: Create complex function callbacks with ease!
|
59
|
+
test_files:
|
60
|
+
- test/Gemfile
|
61
|
+
- test/testit.rb
|
62
|
+
has_rdoc:
|