named_parameters 0.1.4 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/README CHANGED
@@ -6,8 +6,6 @@ Surprisingly it is this easy:
6
6
 
7
7
  gem install named_parameters
8
8
 
9
- ---
10
-
11
9
  == Use it
12
10
  === First, include NamedParameters:
13
11
  class AnyClass
@@ -58,7 +56,7 @@ The call is just the same:
58
56
 
59
57
  == Optional and mandatory parameters
60
58
  You can have both optional and mandatory parameters for the same method.
61
- All paramaters with no default value are mandatory:
59
+ All parameters with no default value are mandatory:
62
60
 
63
61
  class AnyClass
64
62
  named_parameters(a: "default a")
@@ -85,8 +83,52 @@ Blocks are not interfered by named_parameters. You can just use them as usual.
85
83
 
86
84
  any_object.some_method(a: %w(hello world)) {|s| s.upcase!} # => ["HELLO", "WORLD"]
87
85
 
86
+ == Strict mode
87
+ Strict mode means, that keys in the named parameters hash, that are not part of the parameter definition of your
88
+ method, will raise a RuntimeError. You can use it like this:
89
+ class AnyClass
90
+ named_parameters_strict(a: "default a")
91
+ def some_method(a)
92
+ a
93
+ end
94
+ end
88
95
 
89
- ---
96
+ any_object.some_method(a: "given",
97
+ b: "more parameters") # => Error 'Parameter "b" is not accepted by method "some_method"'
98
+
99
+ == Super mode
100
+ Super mode means, that the super method will be called, with the named parameter hash as only parameter. This can be
101
+ useful if some library, that makes use of named parameters, demands you to call super. Remember you cannot access the
102
+ actual named parameters hash. Use the super mode like this:
103
+
104
+ class SomeLibraryClass
105
+ def setup(options)
106
+ #manual options parsing here
107
+ options
108
+ end
109
+ end
110
+
111
+ class AnyClass
112
+ named_parameters_super
113
+ def setup(a,b,c)
114
+ #do something with a, b and c
115
+ end
116
+ end
117
+
118
+ any_object.setup(a: "a",
119
+ b: "b",
120
+ c: "c",
121
+ d: "d") # => {:a => "a", :b => "b", :c => "c", :d => "d"}
122
+
123
+ == Combine super and strict
124
+ In case you need to combine super and strict mode, do:
125
+
126
+ class AnyClass < SomeLibraryClass
127
+ named_parameters_strict_super # or named_parmeters_super_strict
128
+ def some_method(a,b,c)
129
+ # do something with a and b, maybe something with c will be done in super
130
+ end
131
+ end
90
132
 
91
133
  == Acknowledgements
92
134
 
@@ -94,6 +136,12 @@ Kudos to Juris Galang and his gem named-parameters. (http://www.rubygems.org/gem
94
136
  Defenitely have a look at this gem also, if you want to use named parameters.
95
137
 
96
138
  == Changelog
139
+ [1.0.0] * Added strict and super modes
140
+ * Smoother implementation
141
+ * Better structured code
142
+ * More comments
143
+ * Proper around alias instead of rebinding the method
144
+ * Is not strict on default any more
97
145
  [0.1.4] * Changed visibility in documentation
98
146
  [0.1.3] * Made LICENSE visible to documentation (Sorry for that)
99
147
  * Made actual name space and NamedParameters#named_parameters macro visible to documentation
@@ -1,17 +1,7 @@
1
- module Kernel #:nodoc:
2
- # @return [Class] the eigenclass / singletonclass / metaclass of self
3
- def eigenclass
4
- class << self
5
- self
6
- end
7
- end
8
- end
9
-
10
1
  #
11
2
  # Include this module to enable the NamedParameters#named_parameters macro
12
3
  #
13
- module NamedParameters # :nodoc: all
14
-
4
+ module NamedParameters
15
5
 
16
6
  # Makes all methods of this module to class or module methods of include
17
7
  def self.included base #:nodoc:
@@ -24,106 +14,112 @@ module NamedParameters # :nodoc: all
24
14
  private :method_added_before_named_parameters
25
15
  end
26
16
 
27
-
28
- # @param defaults [Hash] Containing defaults values for optional parameters.
29
- # Parameters that are not listet here are mandatory.
30
- def named_parameters (defaults = { }) #:doc:
31
- defaults.empty? ? all_required : some_defaults(defaults)
17
+ def method_added(method_name)
18
+ # redefine the method
19
+ if @redefine_next
20
+ @redefine_next = false
21
+ _redefine_with_named_parameters(method_name,
22
+ @named_parameters_optionals,
23
+ @named_parameters_strict,
24
+ @named_parameters_super)
25
+ end
26
+ # call old method_added
27
+ method_added_before_named_parameters method_name
32
28
  end
33
29
 
34
- private
30
+ # Makes the next method callable via named parameters (options hash)
31
+ # The caller hash may contain keys that are not contained in the parameters of the next method
32
+ # @param [Hash[Symbol,Object]] optionals Optional default values for the parameters
33
+ # Parameters, that are not
34
+ def named_parameters(optionals = { })
35
+ _named_parameters optionals, false, false
36
+ end
35
37
 
36
- # :nodoc:
37
- # [Symbol] is in [:nothing, :all_required, :defaults]
38
- @@next_method_modifier = :nothing
38
+ # Makes the next method callable via named parameters (options hash)
39
+ # The caller hash may only contain keys that are in the parameters of the next method
40
+ # @param [Hash[Symbol,Object]] optionals Optional default values for the parameters
41
+ def named_parameters_strict(optionals = { })
42
+ _named_parameters optionals, true, false
43
+ end
39
44
 
40
- def all_required
41
- @@next_method_modifier = :all_required
45
+ # Makes the next method callable via named parameters (options hash) and calls super with this options hash as the
46
+ # only parameter
47
+ # The caller hash may contain keys that are not contained in the parameters of the next method
48
+ # @param [Hash[Symbol,Object]] optionals Optional default values for the parameters
49
+ def named_parameters_super(optionals = { })
50
+ _named_parameters optionals, false, true
42
51
  end
43
52
 
44
- def some_defaults (defaults)
45
- @@next_method_modifier = :defaults
46
- @@defaults = defaults
53
+ # Makes the next method callable via named parameters (options hash) and calls super with this options hash as the
54
+ # only parameter
55
+ # The caller hash may only contain keys that are in the parameters of the next method
56
+ # @param [Hash[Symbol,Object]] optionals Optional default values for the parameters
57
+ def named_parameters_strict_super(optionals = { })
58
+ _named_parameters optionals, true, true
47
59
  end
48
60
 
61
+ alias_method :named_parameters_super_strict, :named_parameters_strict_super
49
62
 
50
- # @param method_name [Symbol]
51
- def method_added(method_name)
52
- if @@next_method_modifier == :all_required
53
- @@next_method_modifier = :nothing
54
- redefine_all_required_method(method_name)
55
- elsif @@next_method_modifier == :defaults
56
- @@next_method_modifier = :nothing
57
- redefine_defaults_method(method_name, @@defaults)
58
- @@defaults = nil
59
- end
60
- method_added_before_named_parameters method_name
63
+ private
61
64
 
65
+ # @param [Hash[Symbol,Object]] optionals Optional default values for the parameters
66
+ # @param [Boolean] strict Shall keys in the caller options hash, that are not in the parameter list, throw an error?
67
+ # @param [Boolean] call_super Shall super be called with the caller option hash as the only parameter?
68
+ def _named_parameters(optionals, strict, call_super)
69
+ # remember the preferences for the next method, that is added
70
+ @named_parameters_optionals = optionals
71
+ @named_parameters_strict = strict
72
+ @named_parameters_super = call_super
73
+ # make sure only one method is redefined after the named_parameters macro was called
74
+ @redefine_next = true
62
75
  end
63
76
 
64
- # @param method_name [Symbol]
65
- # @return [Object] Not important
66
- def redefine_all_required_method(method_name, &block)
67
- method = instance_method method_name
68
- parameter_definition = method.parameters.collect { |_, b| b }
69
- old_name = (method_name.to_s+"_before_named_parameters").to_sym
70
- alias_method old_name, method_name
71
- module_eval do
72
- define_method method_name do |options, &block|
73
- missing = parameter_definition-(options.keys)
74
- unless missing.empty?
75
- raise RuntimeError.new("Missing arguments: #{missing}")
76
- end
77
- unexpected = options.keys - parameter_definition
78
- unless unexpected.empty?
79
- raise RuntimeError.new("Unexpected arguments: #{unexpected}")
80
- end
81
- parameters = []
82
- parameter_definition.each { |parameter| parameters << options[parameter] }
83
- eval <<-STRING
84
- obj = self
85
- method.bind(obj).call *parameters, &block
86
- STRING
87
- end
77
+ # Redefines a method so the caller can use named parameters instead of ordinary parameters
78
+ # @param [Symbol] method_name
79
+ # @param [Hash[Symbol,Object]] optionals Optional default values for the parameters
80
+ # @param [Boolean] strict Shall keys in the caller options hash, that are not in the parameter list, throw an error?
81
+ # @param [Boolean] call_super Shall super be called with the caller option hash as the only parameter?
82
+ def _redefine_with_named_parameters(method_name, optionals, strict, call_super)
83
+ # the method as defined in the class/module
84
+ original_method ||= instance_method method_name
85
+ # names of the parameters as defined in the class/module
86
+ parameter_names ||= original_method.parameters.collect { |_, b| b }
87
+ new_name_for_original_method ||= (method_name.to_s + '_before_named_parameters').to_sym
88
+
89
+ # "safe" original method
90
+ alias_method new_name_for_original_method, method_name
91
+
92
+ # redefine old method with new logic redirecting to original method
93
+ define_method method_name do |options = { }, &block|
94
+ # check parameters if strict
95
+ _check_for_not_needed_options options, parameter_names, method_name if strict
96
+ # call super if so wanted
97
+ super(options) if call_super
98
+
99
+ # build parameter array from options and optionals
100
+ parameters ||= _parameters_from_options options, optionals, parameter_names, method_name
101
+
102
+ # redirect to original method
103
+ send new_name_for_original_method, *parameters, &block
88
104
  end
89
105
  end
90
106
 
91
- # @param method_name [Symbol]
92
- # @param defaults [Hash]
93
- # @return [Object] Not important
94
- def redefine_defaults_method(method_name, defaults)
95
- method = instance_method method_name
96
- parameter_definition = method.parameters.collect { |_, b| b }
97
-
98
- defaults.keys.each do |default_key|
99
- raise RuntimeError.new("A default value for #{default_key} has been defined,
100
- but it is not a parameter of #{method_name}") unless parameter_definition.include? default_key
107
+ # Builds the actual parameter array for the redirected call to the original method
108
+ def _parameters_from_options(options, optionals, parameter_names, method_name)
109
+ parameters ||= []
110
+ parameter_names.each do |name|
111
+ parameters << options.fetch(name) { optionals.fetch(name) { raise "Mandatory parameter #{name} is not passed to
112
+ method #{method_name}" } }
101
113
  end
114
+ parameters
115
+ end
102
116
 
103
- old_name = (method_name.to_s+"_before_named_parameters").to_sym
104
- alias_method old_name, method_name
105
- module_eval do
106
- define_method method_name do |options = { }, &block|
107
- unexpected = options.keys - parameter_definition
108
- unless unexpected.empty?
109
- raise RuntimeError.new("Unexpected arguments: #{unexpected}")
110
- end
111
-
112
- parameters = []
113
- mandatory = parameter_definition-(defaults.keys)
114
- parameter_definition.each do |parameter|
115
- if mandatory.member? parameter
116
- parameters << options.fetch(parameter) { raise RuntimeError.new("Mandatory parameter #{parameter} not
117
- given") }
118
- else
119
- parameters << options.fetch(parameter, defaults[parameter])
120
- end
121
- end
122
- eval <<-STRING
123
- obj = self
124
- method.bind(obj).call *parameters, &block
125
- STRING
126
- end
117
+ # Checks whether there are keys in the options, that are not in the parameter_names. Throws a user friendly
118
+ # RuntimeError in that case
119
+ def _check_for_not_needed_options(options, parameter_names, method_name)
120
+ options.each do |name, _|
121
+ raise ("Parameter '#{name}' is not accepted by '#{method_name}'") unless parameter_names.member? name
127
122
  end
128
123
  end
129
- end
124
+
125
+ end
@@ -1,104 +1,176 @@
1
- require_relative "../src/named_parameters"
1
+ require_relative '../lib/named_parameters'
2
+
3
+
4
+ class Item
5
+
6
+ attr_reader :id
7
+
8
+ def setup(options = { })
9
+ @id = options[:id]
10
+ end
11
+
12
+ def setup_only_valid_attributes(options = { })
13
+ @id = options[:id]
14
+ end
15
+
16
+ end
17
+
18
+
19
+ class Book < Item
2
20
 
3
- class Book
4
21
  include NamedParameters
5
22
 
6
- attr_reader :author, :title, :year
23
+ attr_reader :author, :title
24
+
7
25
 
8
- named_parameters
26
+ named_parameters(author: 'unknown')
9
27
 
10
- def initialize(author, title, year)
28
+ def edit_attributes(author, title)
11
29
  @author = author
12
30
  @title = title
13
- @year = year
14
31
  end
15
32
 
16
- named_parameters
17
33
 
18
- def foo (bar, baz)
19
- [bar, baz]
34
+ named_parameters_strict(author: 'unknown')
35
+
36
+ def edit_only_valid_attributes(author, title)
37
+ @author = author if author.is_a? String
38
+ @title = title if title.is_a? String
20
39
  end
21
40
 
22
- named_parameters(foo: "foo", bar: "bar")
23
41
 
24
- def method_with_defaults_only (foo, bar)
25
- [foo, bar]
42
+ named_parameters_super(author: 'unknown')
43
+
44
+ def setup(author, title)
45
+ edit_attributes(author: author,
46
+ title: title)
26
47
  end
27
48
 
28
- named_parameters(optional: "optional")
29
- def method_with_defaults_and_mandatory (optional, mandatory)
30
- [optional, mandatory]
49
+ named_parameters_strict_super(author: 'unknown')
50
+
51
+ def setup_only_valid_attributes(author, title, id)
52
+ edit_attributes(author: author,
53
+ title: title)
31
54
  end
32
55
 
33
- named_parameters
56
+ named_parameters(attributes: [])
34
57
 
35
- def method_with_block (array)
36
- array.each { |e| yield e }
58
+ def change_attributes(attributes)
59
+ attributes.each do |attribute|
60
+ instance_variable_set("@#{attribute}", yield(send(attribute)))
61
+ end
37
62
  end
38
63
 
39
64
  end
40
65
 
41
- describe Book do
66
+ describe NamedParameters do
42
67
 
43
68
  attr_reader :book
44
69
  before :each do
45
- @book = Book.new(title: "Enders Game",
46
- author: "Orson Scott Card",
47
- year: 1992)
70
+ @book = Book.new
48
71
  end
49
72
 
73
+ describe 'default' do
74
+ it 'should work with all parameters given' do
75
+ book.edit_attributes(author: 'Orson Scott Card',
76
+ title: 'Enders Game')
77
+ book.title.should == 'Enders Game'
78
+ book.author.should == 'Orson Scott Card'
79
+ end
50
80
 
51
- it "should be a book" do
52
- book.should be_instance_of Book
53
- end
81
+ it 'should work with only mandatory parameters given' do
82
+ book.edit_attributes(title: 'Enders Game')
83
+ book.title.should == 'Enders Game'
84
+ book.author.should == 'unknown'
85
+ end
54
86
 
55
- it "attributes should be readable" do
56
- book.title.should == "Enders Game"
57
- book.author.should == "Orson Scott Card"
58
- book.year.should == 1992
59
- end
87
+ it 'should throw an error when a mandatory parameter misses' do
88
+ -> { book.edit_attributes(author: 'Orson Scott Card') }.should raise_error(RuntimeError)
89
+ end
60
90
 
61
- it "should accept a second method defined" do
62
- book.foo(bar: "bar",
63
- baz: "baz").should == ["bar", "baz"]
91
+ it 'should ignore options that are not part of the parameters' do
92
+ book.edit_attributes(author: 'Orson Scott Card',
93
+ title: 'Enders Game',
94
+ year: 1985)
95
+ book.title.should == 'Enders Game'
96
+ book.author.should == 'Orson Scott Card'
97
+ end
64
98
  end
65
99
 
66
- it "should raise an error on false parameters" do
67
- -> { book.foo(bar: "bar") }.should raise_error(RuntimeError)
68
- -> { book.foo(baz: "baz") }.should raise_error(RuntimeError)
69
- -> { book.foo(bar: "bar",
70
- baz: "baz",
71
- other: "other") }.should raise_error(RuntimeError)
72
- end
73
100
 
74
- it "should accept a method with only optional parameters" do
75
- #book.method_with_defaults_only.should == ["foo", "bar"]
76
- book.method_with_defaults_only(foo: "FOO").should == ["FOO", "bar"]
77
- end
101
+ describe 'strict named parameters' do
78
102
 
79
- it "should not accept inconsistent method definition" do
80
- code = -> do
81
- class Book
82
- some_defaults(a: "a", b: "b")
83
- def a_method (a, c)
84
- #... do something
85
- end
86
- end
103
+ it 'should work with all parameters given' do
104
+ book.edit_only_valid_attributes(author: 'Orson Scott Card',
105
+ title: 'Enders Game')
106
+ book.title.should == 'Enders Game'
107
+ book.author.should == 'Orson Scott Card'
108
+ end
109
+
110
+ it 'should work with only mandatory parameters given' do
111
+ book.edit_only_valid_attributes(title: 'Enders Game')
112
+ book.title.should == 'Enders Game'
113
+ book.author.should == 'unknown'
114
+ end
115
+
116
+ it 'should throw an error when a mandatory parameter misses' do
117
+ -> { book.edit_only_valid_attributes(author: 'Orson Scott Card') }.should raise_error(RuntimeError)
87
118
  end
88
- code.should raise_error(RuntimeError)
119
+
120
+ it 'should throw an error when there are options that are not part of the parameters' do
121
+ -> { book.edit_only_valid_attributes(author: 'Orson Scott Card',
122
+ title: 'Enders Game',
123
+ year: 1985) }.should raise_error(RuntimeError)
124
+ end
125
+
89
126
  end
90
127
 
91
- it "should accept a method with mixed parameters" do
92
- book.method_with_defaults_and_mandatory(mandatory: "given").should == ["optional", "given"]
93
- -> { book.method_with_defaults_and_mandatory(
94
- optional: "only optional is given") }.should raise_error(RuntimeError)
128
+
129
+ describe 'with super' do
130
+
131
+ it 'should call super with all options as the only parameter' do
132
+ book.setup(author: 'Orson Scott Card',
133
+ title: 'Enders Game',
134
+ year: 1985,
135
+ id: 1)
136
+ book.title.should == 'Enders Game'
137
+ book.author.should == 'Orson Scott Card'
138
+ book.id.should == 1
139
+ end
140
+
95
141
  end
96
142
 
97
- it "should accept blocks for methods" do
98
- array = []
99
- book.method_with_block(array: [1, 2, 3]) { |e| array << e }
100
- array.should == [1, 2, 3]
143
+ describe 'with super and strict' do
144
+
145
+ it 'should call super' do
146
+ book.setup_only_valid_attributes(author: 'Orson Scott Card',
147
+ title: 'Enders Game',
148
+ id: 1)
149
+ book.title.should == 'Enders Game'
150
+ book.author.should == 'Orson Scott Card'
151
+ book.id.should == 1
152
+ end
153
+
154
+ it 'should throw error when there are unwanted options' do
155
+ -> { book.setup_only_valid_attributes(author: 'Orson Scott Card',
156
+ title: 'Enders Game',
157
+ year: 1985,
158
+ id: 1) }.should raise_error(RuntimeError)
159
+ end
160
+
101
161
  end
102
162
 
163
+ describe 'with block' do
164
+ it 'should forward blocks' do
165
+ book.edit_attributes(author: 'Orson Scott Card',
166
+ title: 'Enders Game')
167
+ book.change_attributes(attributes: [:author, :title]) do |attribute|
168
+ attribute.to_s.upcase
169
+ end
170
+ book.author.should == 'ORSON SCOTT CARD'
171
+ book.title.should == 'ENDERS GAME'
172
+ end
103
173
 
104
- end
174
+ end
175
+
176
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: named_parameters
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 1.0.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-04-16 00:00:00.000000000 Z
12
+ date: 2012-08-05 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
16
- requirement: &25932288 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -24,7 +24,15 @@ dependencies:
24
24
  version: 3.0.0
25
25
  type: :development
26
26
  prerelease: false
27
- version_requirements: *25932288
27
+ version_requirements: !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: 2.9.0
33
+ - - <
34
+ - !ruby/object:Gem::Version
35
+ version: 3.0.0
28
36
  description: ! 'By including the NamedParameters module into your class/module the
29
37
  ''named_parameters'' macro is
30
38
 
@@ -69,9 +77,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
69
77
  version: '0'
70
78
  requirements: []
71
79
  rubyforge_project:
72
- rubygems_version: 1.8.17
80
+ rubygems_version: 1.8.24
73
81
  signing_key:
74
82
  specification_version: 3
75
83
  summary: Makes your methods callable with named parameters
76
84
  test_files:
77
85
  - spec/named_parameters_spec.rb
86
+ has_rdoc: