parametric 0.0.4 → 0.0.5

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2e111716f0cd0f60d10a0354e70ac7ecfea42634
4
- data.tar.gz: 2012ce66941c5a24f4bf8b3f2dc7e7ceab86311a
3
+ metadata.gz: 4d80f56c0ada8312a25dfcc6ed080b0295f17cfa
4
+ data.tar.gz: 7940d26ce661dfdd7ead181f77ead1979d62413b
5
5
  SHA512:
6
- metadata.gz: f146875b9b83e0b8e85cc89ff04dc817e731653bbda09d0a54c85db1957ef68b6b0007e52a9cda1a704069d9438a4d0b205acc6ce12ef62429f1ab316c4ad800
7
- data.tar.gz: d52634e2b6526a3c81ecef0126a6558351beb030b49f28ae6c07861d2d389d777cbbbfc710f6d32ebf55f9fe6f55b8d9fe8b7fb18b697363f20b7b49c828f767
6
+ metadata.gz: 6ae1bfae9b04299ee38aff8f5be88a15dd505a6a69fd162c2be0ee261d789a2d55795603f7536d9de30df08b5548423cfbeb2db8f05cc0ce64dbc584392d46c3
7
+ data.tar.gz: effe305919dab6e2c4d47636b2711e1ec4600532b3f1429fecfa85234f32a50894cbde7d58334a04de3724759f4948918225ca3c139e9864e3ed0c81fb3275bb
data/README.md CHANGED
@@ -124,6 +124,23 @@ search = OrdersSearch.new
124
124
  search.params[:status] # => ['closed']
125
125
  ```
126
126
 
127
+ ### :nullable fields
128
+
129
+ In same cases you won't want Parametric to provide nil or empty keys for attributes missing from the input. For example when missing keys has special meaning in your application.
130
+
131
+ In those cases you can add the `:nullable` option to said param definitions:
132
+
133
+ ```ruby
134
+ class OrdersSearch
135
+ include Parametric::Params
136
+ param :query, 'Search query. optional', nullable: true
137
+ param :tags, 'Tags', multiple: true
138
+ end
139
+
140
+ search = OrdersSearch.new({})
141
+ search.params # {tags: []}
142
+ ```
143
+
127
144
  ## `available_params`
128
145
 
129
146
  `#available_params` returns the subset of keys that were populated (including defaults). Useful for building query strings.
@@ -172,6 +189,7 @@ search.available_params[:name] # => 'Mr. Ismael'
172
189
 
173
190
  The `Parametric::TypedParams` module includes extra DSL methods to coerce values to standard Ruby types.
174
191
 
192
+ ```ruby
175
193
  class UsersSearch
176
194
  include Parametric::TypedParams
177
195
  integer :age, 'User age'
@@ -180,6 +198,7 @@ class UsersSearch
180
198
  # you can still use :coerce
181
199
  param :name, 'User name', coerce: lambda{|name| "Mr. #{name}"}
182
200
  end
201
+ ```
183
202
 
184
203
  ## Parametric::Hash
185
204
 
@@ -1,4 +1,5 @@
1
1
  require 'ostruct'
2
+ require 'support/class_attribute'
2
3
  module Parametric
3
4
 
4
5
  class ParamsHash < Hash
@@ -13,6 +14,8 @@ module Parametric
13
14
 
14
15
  def self.included(base)
15
16
  base.send(:attr_reader, :params)
17
+ base.class_attribute :_allowed_params
18
+ base._allowed_params = {}
16
19
  base.extend DSL
17
20
  end
18
21
 
@@ -42,7 +45,9 @@ module Parametric
42
45
 
43
46
  def _reduce(raw_params)
44
47
  self.class._allowed_params.each_with_object(ParamsHash.new) do |(key,options),memo|
45
- policy = Policies::Policy.new((raw_params.has_key?(key) ? raw_params[key] : []), options)
48
+ has_key = raw_params.respond_to?(:has_key?) && raw_params.has_key?(key)
49
+ value = has_key ? raw_params[key] : []
50
+ policy = Policies::Policy.new(value, options)
46
51
  policy = policy.wrap(Policies::CoercePolicy) if options[:coerce]
47
52
  policy = policy.wrap(Policies::NestedPolicy) if options[:nested]
48
53
  policy = policy.wrap(Policies::MultiplePolicy) if options[:multiple]
@@ -50,13 +55,19 @@ module Parametric
50
55
  policy = policy.wrap(Policies::MatchPolicy) if options[:match]
51
56
  policy = policy.wrap(Policies::DefaultPolicy) if options.has_key?(:default)
52
57
  policy = policy.wrap(Policies::SinglePolicy) unless options[:multiple]
53
- memo[key] = policy.value
58
+ memo[key] = policy.value unless options[:nullable] && !has_key
54
59
  end
55
60
  end
56
61
 
57
62
  module DSL
58
- def _allowed_params
59
- @allowed_params ||= {}
63
+
64
+ # When subclasses params definitions
65
+ # we want to copy parent class definitions
66
+ # so changes in the child class
67
+ # don't mutate the parent definitions
68
+ #
69
+ def inherited(subclass)
70
+ subclass._allowed_params = self._allowed_params.dup
60
71
  end
61
72
 
62
73
  def param(field_name, label = '', opts = {}, &block)
@@ -72,4 +83,4 @@ module Parametric
72
83
 
73
84
  end
74
85
 
75
- end
86
+ end
@@ -38,7 +38,7 @@ module Parametric
38
38
 
39
39
  class NestedPolicy < Policy
40
40
  def value
41
- decorated.value.map do |v|
41
+ decorated.value.find_all{|v| v.respond_to?(:has_key?)}.map do |v|
42
42
  options[:nested].new(v)
43
43
  end
44
44
  end
@@ -81,4 +81,4 @@ module Parametric
81
81
  end
82
82
 
83
83
  end
84
- end
84
+ end
@@ -1,3 +1,3 @@
1
1
  module Parametric
2
- VERSION = "0.0.4"
2
+ VERSION = "0.0.5"
3
3
  end
@@ -0,0 +1,68 @@
1
+ begin
2
+ require 'active_support/core_ext/class/attribute'
3
+ rescue LoadError
4
+ module Kernel
5
+ # Returns the object's singleton class.
6
+ def singleton_class
7
+ class << self
8
+ self
9
+ end
10
+ end unless respond_to?(:singleton_class) # exists in 1.9.2
11
+
12
+ # class_eval on an object acts like singleton_class.class_eval.
13
+ def class_eval(*args, &block)
14
+ singleton_class.class_eval(*args, &block)
15
+ end
16
+ end
17
+
18
+ class Module
19
+ def remove_possible_method(method)
20
+ if method_defined?(method) || private_method_defined?(method)
21
+ remove_method(method)
22
+ end
23
+ rescue NameError
24
+ # If the requested method is defined on a superclass or included module,
25
+ # method_defined? returns true but remove_method throws a NameError.
26
+ # Ignore this.
27
+ end
28
+
29
+ def redefine_method(method, &block)
30
+ remove_possible_method(method)
31
+ define_method(method, &block)
32
+ end
33
+ end
34
+
35
+ class Class
36
+ def class_attribute(*attrs)
37
+ attrs.each do |name|
38
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
39
+ def self.#{name}() nil end
40
+ def self.#{name}?() !!#{name} end
41
+
42
+ def self.#{name}=(val)
43
+ singleton_class.class_eval do
44
+ remove_possible_method(:#{name})
45
+ define_method(:#{name}) { val }
46
+ end
47
+
48
+ if singleton_class?
49
+ class_eval do
50
+ remove_possible_method(:#{name})
51
+ def #{name}
52
+ defined?(@#{name}) ? @#{name} : singleton_class.#{name}
53
+ end
54
+ end
55
+ end
56
+ val
57
+ end
58
+ RUBY
59
+
60
+ end
61
+ end
62
+
63
+ private
64
+ def singleton_class?
65
+ ancestors.first != self
66
+ end
67
+ end
68
+ end
@@ -89,6 +89,10 @@ describe Parametric do
89
89
  it 'does not accept single option if not in declared options' do
90
90
  klass.new(country: 'USA').params[:country].should be_nil
91
91
  end
92
+
93
+ it 'does not include parameters marked as :nullable' do
94
+ klass.new.params.has_key?(:nullable).should be_false
95
+ end
92
96
  end
93
97
 
94
98
  describe 'TypedParams' do
@@ -103,11 +107,25 @@ describe Parametric do
103
107
  string :country, 'country', options: ['UK', 'CL', 'JPN']
104
108
  string :email, 'email', match: /\w+@\w+\.\w+/
105
109
  array :emails, 'emails', match: /\w+@\w+\.\w+/, default: 'default@email.com'
110
+ param :nullable, 'nullable param', nullable: true
106
111
  end
107
112
  end
108
113
 
109
114
  let(:subject) { klass.new(foo: 'bar', per_page: '20', status: 'four') }
110
115
  it_should_behave_like 'a configurable params object'
116
+
117
+ it 'does not break when value is nil' do
118
+ klass = Class.new do
119
+ include Parametric::TypedParams
120
+ array :friends, 'friends', nullable: true do
121
+ string :name, 'Name'
122
+ end
123
+ end
124
+ klass.new(friends: nil).params[:friends].should == []
125
+ klass.new(friends: []).params[:friends].should == []
126
+ klass.new(friends: [{name: 'foo'}]).params[:friends].first[:name].should == 'foo'
127
+ klass.new.params.has_key?(:friends).should be_false
128
+ end
111
129
  end
112
130
 
113
131
  describe Parametric::Params do
@@ -124,6 +142,7 @@ describe Parametric do
124
142
  param :email, 'email', match: /\w+@\w+\.\w+/
125
143
  param :emails, 'emails', match: /\w+@\w+\.\w+/, multiple: true, default: 'default@email.com'
126
144
  param :available, 'available', default: true
145
+ param :nullable, 'nullable param', nullable: true
127
146
  end
128
147
  end
129
148
 
@@ -132,6 +151,12 @@ describe Parametric do
132
151
  it_should_behave_like 'a configurable params object'
133
152
  end
134
153
 
154
+ describe 'subclassing' do
155
+ let(:subclass){ Class.new(klass) }
156
+ let(:subject){ subclass.new(foo: 'bar', per_page: 20, status: 'four') }
157
+ it_should_behave_like 'a configurable params object'
158
+ end
159
+
135
160
  describe '#available_params' do
136
161
  let(:subject) { klass.new(foo: 'bar', name: 'lala', per_page: 20, status: 'four', emails: 'one@email.com,two@email.com') }
137
162
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: parametric
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ismael Celis
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-06-19 00:00:00.000000000 Z
11
+ date: 2014-07-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -74,6 +74,7 @@ files:
74
74
  - lib/parametric/typed_params.rb
75
75
  - lib/parametric/utils.rb
76
76
  - lib/parametric/version.rb
77
+ - lib/support/class_attribute.rb
77
78
  - parametric.gemspec
78
79
  - spec/nested_params_spec.rb
79
80
  - spec/parametric_spec.rb