attr_encodable 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +118 -86
- data/lib/attr_encodable.rb +1 -137
- data/lib/encodable.rb +10 -0
- data/lib/encodable/active_record.rb +7 -0
- data/lib/encodable/active_record/class_methods.rb +100 -0
- data/lib/encodable/active_record/instance_methods.rb +65 -0
- data/lib/encodable/array.rb +12 -0
- data/spec/attr_encodable_spec.rb +20 -4
- metadata +6 -1
data/README.md
CHANGED
@@ -1,142 +1,174 @@
|
|
1
|
-
attr_encodable
|
2
|
-
=
|
1
|
+
# attr_encodable
|
3
2
|
|
4
3
|
Never override `as_json` again! **attr_encodable** adds attribute black- or white-listing for ActiveRecord serialization, as well as default serialization options. This is especially useful for protecting private attributes when building a public API.
|
5
4
|
|
6
|
-
Install
|
7
|
-
==
|
5
|
+
## Install
|
8
6
|
|
9
|
-
|
7
|
+
Bundler:
|
10
8
|
|
11
|
-
gem
|
9
|
+
gem 'attr_encodable'
|
12
10
|
|
13
|
-
|
11
|
+
Rubygems:
|
14
12
|
|
15
|
-
gem
|
13
|
+
gem install attr_encodable
|
16
14
|
|
17
|
-
Install in Rails 2.x (in your environment.rb file)
|
18
15
|
|
19
|
-
|
16
|
+
## Usage
|
20
17
|
|
21
|
-
|
22
|
-
==
|
18
|
+
### White-listing
|
23
19
|
|
24
|
-
White-listing
|
25
|
-
===
|
26
20
|
|
27
21
|
You can whitelist or blacklist attributes for serialization using the `attr_encodable` and `attr_unencodable` class methods. Let's look at an example. For this example, we'll use the following classes:
|
28
22
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
23
|
+
```ruby
|
24
|
+
class User < ActiveRecord::Base
|
25
|
+
has_many :permissions
|
26
|
+
validates_presence_of :email, :password
|
27
|
+
|
28
|
+
def foobar
|
29
|
+
"baz"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Permission < ActiveRecord::Base
|
34
|
+
belongs_to :user
|
35
|
+
validates_presence_of :name, :user
|
36
|
+
|
37
|
+
def hello
|
38
|
+
"World!"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
```
|
46
42
|
|
47
43
|
... with the following schema:
|
48
44
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
45
|
+
```ruby
|
46
|
+
create_table :permissions, :force => true do |t|
|
47
|
+
t.belongs_to :user
|
48
|
+
t.string :name
|
49
|
+
end
|
50
|
+
|
51
|
+
create_table :users, :force => true do |t|
|
52
|
+
t.string :login, :limit => 48
|
53
|
+
t.string :email, :limit => 128
|
54
|
+
t.string :name, :limit => 32
|
55
|
+
t.string :password, :limit => 60
|
56
|
+
t.boolean :admin, :default => false
|
57
|
+
end
|
58
|
+
```
|
61
59
|
|
62
60
|
Let's make a user and try encoding them:
|
63
61
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
62
|
+
```ruby
|
63
|
+
@user = User.create(:name => "Flip", :email => "flip@x451.com", :password => "awesomesauce", :admin => true)
|
64
|
+
#=> #<User id: 1, login: nil, email: "flip@x451.com", name: "Flip", password: "awesomesauce", admin: true>
|
65
|
+
@user.to_json
|
66
|
+
#=> {"name":"Flip","admin":true,"id":1,"password":"awesomesauce","login":null,"email":"flip@x451.com"}
|
67
|
+
```
|
69
68
|
Trouble is, we don't want their admin status OR their password coming through in our API. So why not protect their information a little bit?
|
70
69
|
|
71
|
-
|
72
|
-
|
73
|
-
|
70
|
+
```ruby
|
71
|
+
User.attr_encodable :id, :name, :login, :email
|
72
|
+
@user.to_json
|
73
|
+
#=> {"name":"Flip","id":1,"login":null,"email":"flip@x451.com"}
|
74
|
+
```
|
74
75
|
|
75
76
|
Ah, that's so much better! Now whenever we encode a user instance we'll be showing only some default information.
|
76
77
|
|
77
78
|
`attr_unencodable` is similar, except that it bans an attribute. Following along with the example above, if we then called `attr_unencodable`, we could
|
78
79
|
restrict our user's information even more. Let's say I don't want my e-mail getting out:
|
79
80
|
|
80
|
-
|
81
|
-
|
82
|
-
|
81
|
+
```ruby
|
82
|
+
User.attr_unencodable :email
|
83
|
+
@user.to_json
|
84
|
+
#=> {"name":"Flip","id":1,"login":null}
|
85
|
+
```
|
83
86
|
|
84
87
|
Alright! Now you can't see my e-mail. Sucker.
|
85
88
|
|
86
|
-
Default `:include` and `:method` options
|
87
|
-
===
|
88
|
-
|
89
|
-
`to_json` isn't just concerned with attributes. It also supports `:include`, which includes a relationship with `to_json` called on **it**, as well `:methods`, which adds the result of calling methods on the instance as well.
|
89
|
+
### Default `:include` and `:method` options
|
90
90
|
|
91
|
-
|
91
|
+
`to_json` isn't just concerned with attributes. It also supports `:include`, which includes a relationship with `to_json` called on **it**, as well as `:methods`, which adds the result of calling one or more methods on the instance. `attr_encodable` supports both without specifying what you want to call; just include them in your list:
|
92
92
|
|
93
|
-
|
94
|
-
|
95
|
-
|
93
|
+
```ruby
|
94
|
+
User.attr_encodable :foobar
|
95
|
+
@user.to_json
|
96
|
+
#=> {"name":"Flip","foobar":"baz","id":1,"login":null}
|
97
|
+
```
|
96
98
|
|
97
99
|
With includes, our example might look like this:
|
98
100
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
101
|
+
```ruby
|
102
|
+
class User < ActiveRecord::Base
|
103
|
+
attr_encodable :id, :name, :login, :permissions
|
104
|
+
has_many :permissions
|
105
|
+
end
|
103
106
|
|
104
|
-
|
105
|
-
|
107
|
+
@user.to_json
|
108
|
+
#=> {"name":"Flip","foobar":"baz","id":1,"login":null,"permissions":[]}
|
109
|
+
```
|
106
110
|
|
107
|
-
Neato! And of course, when `:permissions` is serialized, it will take into account any `attr_encodable` settings the
|
111
|
+
Neato! And of course, when `:permissions` is serialized, it will take into account any `attr_encodable` settings the `Permission` class has!
|
108
112
|
|
109
|
-
Renaming Attributes
|
110
|
-
===
|
113
|
+
### Renaming Attributes
|
111
114
|
|
112
115
|
Sometimes you don't want an attribute to come out in JSON named what it's named in the database. There are two options you can pursue here.
|
113
116
|
|
114
|
-
Prefix it!
|
115
|
-
====
|
117
|
+
#### Prefix it!
|
116
118
|
|
117
119
|
**attr_encodable** supports prefixing of attribute names. Just pass an options hash onto the end of the method with a :prefix key and you're good to go. Example:
|
118
120
|
|
119
|
-
|
120
|
-
|
121
|
-
|
121
|
+
```ruby
|
122
|
+
class User < ActiveRecord::Base
|
123
|
+
attr_encodable :ed, :prefix => :i_will_hunt
|
124
|
+
end
|
122
125
|
|
123
|
-
|
124
|
-
|
126
|
+
@user.to_json
|
127
|
+
=> {"i_will_hunt_ed":true}
|
128
|
+
```
|
125
129
|
|
126
|
-
Rename it completely!
|
127
|
-
====
|
130
|
+
#### Rename it completely!
|
128
131
|
|
129
132
|
If you don't want to prefix, just rename the whole damn thing:
|
130
133
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
134
|
+
```ruby
|
135
|
+
class User < ActiveRecord::Base
|
136
|
+
attr_encodable :admin => :superuser
|
137
|
+
end
|
138
|
+
|
139
|
+
@user.to_json
|
140
|
+
#=> {"superuser":true}
|
141
|
+
```
|
137
142
|
|
138
143
|
Renaming and prefixing work for any `:include` and `:methods` arguments you pass in as well!
|
139
144
|
|
145
|
+
### NEW! `attr_encodable` groups
|
146
|
+
|
147
|
+
Soemtimes you may want to supply more information or less information, depending on the context. For example, if your API supports listing multiple records and viewing individual records, you may want to list multiple records with just enough information to get them to a URL where they can visit the individual record in detail. In that case, you can create a group using an `:as` option:
|
148
|
+
|
149
|
+
```ruby
|
150
|
+
User.attr_encodable :login, :name, :email
|
151
|
+
User.attr_encodable :login, :as => :listing
|
152
|
+
```
|
153
|
+
|
154
|
+
This will create two groups: the default group, which is how your User will normally be serialized when you call `as_json` or `to_json` on it. Then, the `:listing` group, which can be used like so:
|
155
|
+
|
156
|
+
```ruby
|
157
|
+
@user.as_json #=> {"login": "flipsasser", "email": "support@getplinq.com", "name": "Flip Sasser"}
|
158
|
+
@user.as_json(:listing) #=> {"login": "flipsasser"}
|
159
|
+
```
|
160
|
+
|
161
|
+
This comes in super handy when you want a quick way to limit or expand data in certain situations.
|
162
|
+
|
163
|
+
To flip the example around, imagine you wanted to default to a very limited set of information, but expand it in a certain situation:
|
164
|
+
|
165
|
+
```ruby
|
166
|
+
User.attr_encodable :login
|
167
|
+
User.attr_encodable :login, :admin, :email, :password, :as => :admin_api
|
168
|
+
```
|
169
|
+
|
170
|
+
Now you can call `@user.to_json(:admin_api)` somewhere, which will include a full users' details, but any other `as_json` call will keep that information private.
|
171
|
+
|
140
172
|
Okay, that's all. Thanks for stopping by.
|
141
173
|
|
142
174
|
Copyright © 2011 Flip Sasser
|
data/lib/attr_encodable.rb
CHANGED
@@ -1,137 +1 @@
|
|
1
|
-
require '
|
2
|
-
|
3
|
-
module Encodable
|
4
|
-
module ClassMethods
|
5
|
-
def attr_encodable(*attributes)
|
6
|
-
prefix = begin
|
7
|
-
if attributes.last.is_a?(Hash)
|
8
|
-
attributes.last.assert_valid_keys(:prefix)
|
9
|
-
prefix = attributes.extract_options![:prefix]
|
10
|
-
end
|
11
|
-
rescue ArgumentError
|
12
|
-
end
|
13
|
-
unless @encodable_whitelist_started
|
14
|
-
# Since we're white-listing, make sure we black-list every attribute to begin with
|
15
|
-
unencodable_attributes.push *column_names.map(&:to_sym)
|
16
|
-
@encodable_whitelist_started = true
|
17
|
-
end
|
18
|
-
stash_encodable_attribute = lambda {|method, value|
|
19
|
-
if prefix
|
20
|
-
value = "#{prefix}_#{value}"
|
21
|
-
end
|
22
|
-
method = method.to_sym
|
23
|
-
value = value.to_sym
|
24
|
-
renamed_encoded_attributes.merge!({method => value}) if method != value
|
25
|
-
# Un-black-list any attribute we white-listed
|
26
|
-
unencodable_attributes.delete method
|
27
|
-
default_attributes.push method
|
28
|
-
}
|
29
|
-
attributes.each do |attribute|
|
30
|
-
if attribute.is_a?(Hash)
|
31
|
-
attribute.each do |method, value|
|
32
|
-
stash_encodable_attribute.call(method, value)
|
33
|
-
end
|
34
|
-
else
|
35
|
-
stash_encodable_attribute.call(attribute, attribute)
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
def attr_unencodable(*attributes)
|
41
|
-
unencodable_attributes.push *attributes.map(&:to_sym)
|
42
|
-
end
|
43
|
-
|
44
|
-
def default_attributes
|
45
|
-
@default_attributes ||= begin
|
46
|
-
default_attributes = []
|
47
|
-
superk = superclass
|
48
|
-
while superk.respond_to?(:default_attributes)
|
49
|
-
default_attributes.push(*superk.default_attributes)
|
50
|
-
superk = superk.superclass
|
51
|
-
end
|
52
|
-
default_attributes
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
def renamed_encoded_attributes
|
57
|
-
@renamed_encoded_attributes ||= begin
|
58
|
-
renamed_encoded_attributes = {}
|
59
|
-
superk = superclass
|
60
|
-
while superk.respond_to?(:renamed_encoded_attributes)
|
61
|
-
renamed_encoded_attributes.merge!(superk.renamed_encoded_attributes)
|
62
|
-
superk = superk.superclass
|
63
|
-
end
|
64
|
-
renamed_encoded_attributes
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
def unencodable_attributes
|
69
|
-
@unencodable_attributes ||= begin
|
70
|
-
unencodable_attributes = []
|
71
|
-
superk = superclass
|
72
|
-
while superk.respond_to?(:unencodable_attributes)
|
73
|
-
unencodable_attributes.push(*superk.unencodable_attributes)
|
74
|
-
superk = superk.superclass
|
75
|
-
end
|
76
|
-
unencodable_attributes
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
module InstanceMethods
|
82
|
-
def serializable_hash(options = {})
|
83
|
-
options ||= {}
|
84
|
-
original_except = if options[:except]
|
85
|
-
options[:except] = Array(options[:except]).map(&:to_sym)
|
86
|
-
else
|
87
|
-
options[:except] = []
|
88
|
-
end
|
89
|
-
|
90
|
-
# Convert :only to :except
|
91
|
-
if options && options[:only]
|
92
|
-
options[:except].push *self.class.default_attributes - Array(options.delete(:only).map(&:to_sym))
|
93
|
-
end
|
94
|
-
|
95
|
-
# This is a little bit confusing. ActiveRecord's default behavior is to apply the :except arguments you pass
|
96
|
-
# in to any :include options UNLESS it's overridden on the :include option. In the event that we have some
|
97
|
-
# *default* excepts that come from Encodable, we want to ignore those and pass only whatever the original
|
98
|
-
# :except options from the user were on down to the :include guys.
|
99
|
-
inherited_except = original_except - self.class.default_attributes
|
100
|
-
case options[:include]
|
101
|
-
when Array, Symbol
|
102
|
-
# Convert includes arrays or singleton symbols into a hash with our original_except scope
|
103
|
-
includes = Array(options[:include])
|
104
|
-
options[:include] = Hash[*includes.map{|association| [association, {:except => inherited_except}]}.flatten]
|
105
|
-
else
|
106
|
-
options[:include] ||= {}
|
107
|
-
end
|
108
|
-
# Exclude the black-list
|
109
|
-
options[:except].push *self.class.unencodable_attributes
|
110
|
-
# Include any default :include or :methods arguments that were passed in earlier
|
111
|
-
self.class.default_attributes.each do |attribute, as|
|
112
|
-
unless options[:except].include?(attribute)
|
113
|
-
if association = self.class.reflect_on_association(attribute)
|
114
|
-
options[:include][attribute] = {:except => inherited_except}
|
115
|
-
elsif respond_to?(attribute) && !self.class.column_names.include?(attribute.to_s)
|
116
|
-
options[:methods] ||= Array(options[:methods]).compact
|
117
|
-
options[:methods].push attribute
|
118
|
-
end
|
119
|
-
end
|
120
|
-
end
|
121
|
-
as_json = super(options)
|
122
|
-
unless self.class.renamed_encoded_attributes.empty?
|
123
|
-
self.class.renamed_encoded_attributes.each do |attribute, as|
|
124
|
-
if as_json.has_key?(attribute) || as_json.has_key?(attribute.to_s)
|
125
|
-
as_json[as.to_s] = as_json.delete(attribute) || as_json.delete(attribute.to_s)
|
126
|
-
end
|
127
|
-
end
|
128
|
-
end
|
129
|
-
as_json
|
130
|
-
end
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
|
-
if defined? ActiveRecord::Base
|
135
|
-
ActiveRecord::Base.extend Encodable::ClassMethods
|
136
|
-
ActiveRecord::Base.send(:include, Encodable::InstanceMethods)
|
137
|
-
end
|
1
|
+
require 'encodable'
|
data/lib/encodable.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
module Encodable
|
2
|
+
autoload(:ActiveRecord, 'encodable/active_record')
|
3
|
+
autoload(:Array, 'encodable/array')
|
4
|
+
end
|
5
|
+
|
6
|
+
if defined? ActiveRecord::Base
|
7
|
+
ActiveRecord::Base.extend Encodable::ActiveRecord::ClassMethods
|
8
|
+
ActiveRecord::Base.send :include, Encodable::ActiveRecord::InstanceMethods
|
9
|
+
require 'encodable/array'
|
10
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module Encodable
|
2
|
+
module ActiveRecord
|
3
|
+
module ClassMethods
|
4
|
+
def attr_encodable(*attributes)
|
5
|
+
options = extract_encodable_options!(attributes)
|
6
|
+
|
7
|
+
unless @encodable_whitelist_started
|
8
|
+
# Since we're white-listing, make sure we black-list every attribute to begin with
|
9
|
+
unencodable_attributes(options[:as]).push *column_names.map(&:to_sym)
|
10
|
+
@encodable_whitelist_started = true
|
11
|
+
end
|
12
|
+
|
13
|
+
attributes.each do |attribute|
|
14
|
+
if attribute.is_a?(Hash)
|
15
|
+
attribute.each do |method, value|
|
16
|
+
add_encodable_attribute(method, value, options)
|
17
|
+
end
|
18
|
+
else
|
19
|
+
add_encodable_attribute(attribute, attribute, options)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def add_encodable_attribute(method, value, options = {})
|
25
|
+
value = "#{options[:prefix]}_#{value}" if options[:prefix]
|
26
|
+
method = method.to_sym
|
27
|
+
value = value.to_sym
|
28
|
+
renamed_encoded_attributes(options[:as]).merge!({method => value}) if method != value
|
29
|
+
# Un-black-list any attribute we white-listed
|
30
|
+
unencodable_attributes(options[:as]).delete method
|
31
|
+
default_attributes(options[:as]).push method
|
32
|
+
end
|
33
|
+
|
34
|
+
def attr_unencodable(*attributes)
|
35
|
+
options = extract_encodable_options!(attributes)
|
36
|
+
unencodable_attributes(options[:as]).push *attributes.map(&:to_sym)
|
37
|
+
end
|
38
|
+
|
39
|
+
def default_attributes(name = nil)
|
40
|
+
@default_attributes ||= merge_encodable_superclass_options(:default_attributes, [])
|
41
|
+
if name
|
42
|
+
@default_attributes[name] ||= []
|
43
|
+
else
|
44
|
+
@default_attributes
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def encodable_sets
|
49
|
+
@encodable_sets
|
50
|
+
end
|
51
|
+
|
52
|
+
def renamed_encoded_attributes(name = nil)
|
53
|
+
@renamed_encoded_attributes ||= merge_encodable_superclass_options(:renamed_encoded_attributes, {})
|
54
|
+
if name
|
55
|
+
@renamed_encoded_attributes[name] ||= {}
|
56
|
+
else
|
57
|
+
@renamed_encoded_attributes
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def unencodable_attributes(name = nil)
|
62
|
+
@unencodable_attributes ||= merge_encodable_superclass_options(:unencodable_attributes, [])
|
63
|
+
if name
|
64
|
+
@unencodable_attributes[name] ||= []
|
65
|
+
else
|
66
|
+
@unencodable_attributes
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
def extract_encodable_options!(attributes)
|
72
|
+
begin
|
73
|
+
attributes.last.assert_valid_keys(:prefix, :as)
|
74
|
+
options = attributes.extract_options!
|
75
|
+
rescue ArgumentError
|
76
|
+
end if attributes.last.is_a?(Hash)
|
77
|
+
|
78
|
+
options ||= {}
|
79
|
+
options[:as] ||= :default
|
80
|
+
options
|
81
|
+
end
|
82
|
+
|
83
|
+
def merge_encodable_superclass_options(method, default)
|
84
|
+
value = {}
|
85
|
+
superk = superclass
|
86
|
+
while superk.respond_to?(method)
|
87
|
+
supervalue = superk.send(method)
|
88
|
+
case default
|
89
|
+
when Array
|
90
|
+
supervalue.each {|name, default| (value[name] ||= []).push *default }
|
91
|
+
when Hash
|
92
|
+
supervalue.each {|name, default| (value[name] ||= {}).merge! default }
|
93
|
+
end
|
94
|
+
superk = superk.superclass
|
95
|
+
end
|
96
|
+
value
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Encodable
|
2
|
+
module ActiveRecord
|
3
|
+
module InstanceMethods
|
4
|
+
def as_json(name = nil, options = nil)
|
5
|
+
case name
|
6
|
+
when Hash, NilClass
|
7
|
+
options = name
|
8
|
+
when String, Symbol
|
9
|
+
(options ||= {}).merge! :as => name
|
10
|
+
end
|
11
|
+
super options
|
12
|
+
end
|
13
|
+
|
14
|
+
def serializable_hash(options = {})
|
15
|
+
options ||= {}
|
16
|
+
options[:as] ||= :default
|
17
|
+
|
18
|
+
original_except = if options[:except]
|
19
|
+
options[:except] = Array(options[:except]).map(&:to_sym)
|
20
|
+
else
|
21
|
+
options[:except] = []
|
22
|
+
end
|
23
|
+
|
24
|
+
# Convert :only to :except
|
25
|
+
if options && options[:only]
|
26
|
+
options[:except].push *self.class.default_attributes(options[:as]) - Array(options.delete(:only).map(&:to_sym))
|
27
|
+
end
|
28
|
+
|
29
|
+
# This is a little bit confusing. ActiveRecord's default behavior is to apply the :except arguments you pass
|
30
|
+
# in to any :include options UNLESS it's overridden on the :include option. In the event that we have some
|
31
|
+
# *default* excepts that come from Encodable, we want to ignore those and pass only whatever the original
|
32
|
+
# :except options from the user were on down to the :include guys.
|
33
|
+
inherited_except = original_except - self.class.default_attributes(options[:as])
|
34
|
+
case options[:include]
|
35
|
+
when Array, Symbol
|
36
|
+
# Convert includes arrays or singleton symbols into a hash with our original_except scope
|
37
|
+
includes = Array(options[:include])
|
38
|
+
options[:include] = Hash[*includes.map{|association| [association, {:except => inherited_except}]}.flatten]
|
39
|
+
else
|
40
|
+
options[:include] ||= {}
|
41
|
+
end
|
42
|
+
# Exclude the black-list
|
43
|
+
options[:except].push *self.class.unencodable_attributes(options[:as])
|
44
|
+
# Include any default :include or :methods arguments that were passed in earlier
|
45
|
+
self.class.default_attributes(options[:as]).each do |attribute, as|
|
46
|
+
unless options[:except].include?(attribute)
|
47
|
+
if association = self.class.reflect_on_association(attribute)
|
48
|
+
options[:include][attribute] = {:except => inherited_except}
|
49
|
+
elsif respond_to?(attribute) && !self.class.column_names.include?(attribute.to_s)
|
50
|
+
options[:methods] ||= Array(options[:methods]).compact
|
51
|
+
options[:methods].push attribute
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
as_json = super(options)
|
56
|
+
self.class.renamed_encoded_attributes(options[:as]).each do |attribute, as|
|
57
|
+
if as_json.has_key?(attribute) || as_json.has_key?(attribute.to_s)
|
58
|
+
as_json[as.to_s] = as_json.delete(attribute) || as_json.delete(attribute.to_s)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
as_json
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
Array.class_eval do
|
2
|
+
def as_json_with_encodable(name = nil, options = nil)
|
3
|
+
case name
|
4
|
+
when Hash, NilClass
|
5
|
+
options = name
|
6
|
+
when String, Symbol
|
7
|
+
(options ||= {}).merge! :as => name
|
8
|
+
end
|
9
|
+
as_json_without_encodable options
|
10
|
+
end
|
11
|
+
alias_method_chain :as_json, :encodable
|
12
|
+
end
|
data/spec/attr_encodable_spec.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
require
|
1
|
+
require 'active_record'
|
2
2
|
require 'active_support'
|
3
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'attr_encodable')
|
3
4
|
|
4
5
|
describe Encodable do
|
5
6
|
it "should automatically extend ActiveRecord::Base" do
|
@@ -61,11 +62,11 @@ describe Encodable do
|
|
61
62
|
end
|
62
63
|
|
63
64
|
it "should favor whitelisting to blacklisting" do
|
64
|
-
User.unencodable_attributes.should == []
|
65
|
+
User.unencodable_attributes(:default).should == []
|
65
66
|
User.attr_unencodable 'foo', 'bar', 'baz'
|
66
|
-
User.unencodable_attributes.should == [:foo, :bar, :baz]
|
67
|
+
User.unencodable_attributes(:default).should == [:foo, :bar, :baz]
|
67
68
|
User.attr_encodable :id, :first_name
|
68
|
-
User.unencodable_attributes.map(&:to_s).should == ['foo', 'bar', 'baz'] + User.column_names - ['id', 'first_name']
|
69
|
+
User.unencodable_attributes(:default).map(&:to_s).should == ['foo', 'bar', 'baz'] + User.column_names - ['id', 'first_name']
|
69
70
|
end
|
70
71
|
|
71
72
|
describe "at the parent model level" do
|
@@ -202,4 +203,19 @@ describe Encodable do
|
|
202
203
|
class SubUser < User; end
|
203
204
|
SubUser.unencodable_attributes.should == User.unencodable_attributes
|
204
205
|
end
|
206
|
+
|
207
|
+
describe "named groups" do
|
208
|
+
it "should be supported on a class-basis with a :name option" do
|
209
|
+
User.attr_unencodable :id
|
210
|
+
User.all.as_json.should == [@user.attributes.except('id')]
|
211
|
+
User.attr_encodable :id, :first_name, :last_name, :as => :short
|
212
|
+
User.all.as_json(:short).should == [{'id' => 1, 'first_name' => 'flip', 'last_name' => 'sasser'}]
|
213
|
+
end
|
214
|
+
|
215
|
+
it "should be supported on an instance-basis with a :name option" do
|
216
|
+
User.attr_encodable :id, :first_name, :last_name, :as => :short
|
217
|
+
@user.as_json.should == @user.attributes
|
218
|
+
@user.as_json(:short).should == {'id' => 1, 'first_name' => 'flip', 'last_name' => 'sasser'}
|
219
|
+
end
|
220
|
+
end
|
205
221
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: attr_encodable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -40,6 +40,11 @@ files:
|
|
40
40
|
- LICENSE
|
41
41
|
- README.md
|
42
42
|
- lib/attr_encodable.rb
|
43
|
+
- lib/encodable.rb
|
44
|
+
- lib/encodable/active_record.rb
|
45
|
+
- lib/encodable/active_record/class_methods.rb
|
46
|
+
- lib/encodable/active_record/instance_methods.rb
|
47
|
+
- lib/encodable/array.rb
|
43
48
|
- spec/attr_encodable_spec.rb
|
44
49
|
homepage: http://github.com/Plinq/attr_encodable
|
45
50
|
licenses: []
|