named_parameters 0.1.4 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: