serialize_with_options 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ test.db
2
+ *.gem
3
+ .bundle
4
+ Gemfile.lock
5
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in serialize_with_options.gemspec
4
+ gemspec
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 [name of plugin creator]
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,80 @@
1
+ SerializeWithOptions
2
+ ====================
3
+
4
+ This plugin is designed to make creating XML and JSON APIs for your Rails apps dead simple. We noticed a lot of repetition when creating API responses in our controllers. With this plugin, you can set the serialization options for a model with a simple DSL, rather than repeating them in every controller that includes it.
5
+
6
+
7
+ Example
8
+ -------
9
+
10
+ Here is a simple example of SerializeWithOptions in action:
11
+
12
+ class User < ActiveRecord::Base
13
+ has_many :posts
14
+
15
+ serialize_with_options do
16
+ methods :post_count
17
+ includes :posts
18
+ except :email
19
+ end
20
+
21
+ serialize_with_options(:with_email) do
22
+ methods :post_count
23
+ includes :posts
24
+ end
25
+
26
+ def post_count
27
+ self.posts.count
28
+ end
29
+ end
30
+
31
+ class Post < ActiveRecord::Base
32
+ has_many :comments
33
+ belongs_to :user
34
+
35
+ serialize_with_options do
36
+ only :title
37
+ includes :user, :comments
38
+ end
39
+ end
40
+
41
+ class Comment < ActiveRecord::Base
42
+ belongs_to :post
43
+ end
44
+
45
+ With these directives in place, we can call `@post.to_xml` (or `@post.to_json`) and it's as if we entered:
46
+
47
+ @post.to_xml(:include => { :user => { :methods => :post_count, :except => :email }, :comments => { } })
48
+
49
+ In our controller, we can just say:
50
+
51
+ def show
52
+ @post = Post.find(params[:id])
53
+
54
+ respond_to do |format|
55
+ format.html
56
+ format.xml { render :xml => @post }
57
+ format.json { render :json => @post }
58
+ end
59
+ end
60
+
61
+ All serialization options are enclosed in a `serialize_with_options` block. There are four options, lifted directly from ActiveRecord's [serialization API][ser]: `methods` are the methods to add to the default attributes, `only` are the attributes to include, excluding all others, `except` are the attributes to leave out, and `includes` are the associated models.
62
+
63
+ If an included model has its own `serialize_with_options` block, its `methods`, `only`, and `except` will be respected. However, the included model's `includes` directive will be ignored (only one level of nesting is supported). If you need more than one level of nesting, you can use a hash to set your included models, rather than an array.
64
+
65
+ The `serialize_with_options` class method takes an optional argument for naming a configuration set (see the User model above). This is useful if you need to multiple serialization configuration sets. You can access these secondary configuration sets by passing the set name to the serialization method, e.g., `@post.to_xml(:with_email)`.
66
+
67
+
68
+ Installation
69
+ ------------
70
+
71
+ From your app root:
72
+
73
+ script/plugin install git://github.com/vigetlabs/serialize_with_options.git
74
+
75
+ * * *
76
+
77
+ Copyright (c) 2009-2010 David Eisinger ([Viget Labs][vgt]), released under the MIT license.
78
+
79
+ [ser]: http://api.rubyonrails.org/classes/ActiveRecord/Serialization.html
80
+ [vgt]: http://www.viget.com/
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require 'bundler'
2
+ require 'rake/testtask'
3
+ Bundler::GemHelper.install_tasks
4
+
5
+ Rake::TestTask.new(:test) do |test|
6
+ test.libs << 'lib' << 'test'
7
+ test.pattern = 'test/*.rb'
8
+ test.verbose = true
9
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ ActiveRecord::Base.extend(SerializeWithOptions)
@@ -0,0 +1,3 @@
1
+ module SerializeWithOptions
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,88 @@
1
+ module SerializeWithOptions
2
+
3
+ def serialize_with_options(set = :default, &block)
4
+ configuration = read_inheritable_attribute(:configuration) || {}
5
+ options = read_inheritable_attribute(:options) || {}
6
+
7
+ configuration[set] = Config.new.instance_eval(&block)
8
+
9
+ write_inheritable_attribute :configuration, configuration
10
+ write_inheritable_attribute :options, options
11
+
12
+ include InstanceMethods
13
+ end
14
+
15
+ def serialization_configuration(set)
16
+ configuration = read_inheritable_attribute(:configuration)
17
+ conf = if configuration
18
+ configuration[set] || configuration[:default]
19
+ end
20
+
21
+ conf.try(:dup) || { :methods => nil, :only => nil, :except => nil }
22
+ end
23
+
24
+ def serialization_options(set)
25
+ options = read_inheritable_attribute(:options)
26
+ options[set] ||= serialization_configuration(set).tap do |opts|
27
+ includes = opts.delete(:includes)
28
+
29
+ if includes
30
+ opts[:include] = includes.inject({}) do |hash, class_name|
31
+ if class_name.is_a? Hash
32
+ hash.merge(class_name)
33
+ else
34
+ begin
35
+ klass = class_name.to_s.classify.constantize
36
+ hash[class_name] = klass.serialization_configuration(set)
37
+ hash[class_name][:include] = nil if hash[class_name].delete(:includes)
38
+ hash
39
+ rescue NameError
40
+ hash.merge(class_name => { :include => nil })
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ write_inheritable_attribute :options, options
47
+ options[set]
48
+ end
49
+
50
+ class Config
51
+ undef_method :methods
52
+ Instructions = [:skip_instruct, :dasherize, :skip_types, :root_in_json].freeze
53
+
54
+ def initialize
55
+ @data = { :methods => nil, :only => nil, :except => nil }
56
+ end
57
+
58
+ def method_missing(method, *args)
59
+ @data[method] = Instructions.include?(method) ? args.first : args
60
+ @data
61
+ end
62
+ end
63
+
64
+ module InstanceMethods
65
+ def to_xml(opts = {})
66
+ set, opts = parse_serialization_options(opts)
67
+ super(self.class.serialization_options(set).deep_merge(opts))
68
+ end
69
+
70
+ def to_json(opts = {})
71
+ set, opts = parse_serialization_options(opts)
72
+ super(self.class.serialization_options(set).deep_merge(opts))
73
+ end
74
+
75
+ private
76
+
77
+ def parse_serialization_options(opts)
78
+ if opts.is_a? Symbol
79
+ set = opts
80
+ opts = {}
81
+ else
82
+ set = :default
83
+ end
84
+
85
+ [set, opts]
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "serialize_with_options/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "serialize_with_options"
7
+ s.version = SerializeWithOptions::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["David Eisinger"]
10
+ s.email = ["david.eisinger@gmail.com"]
11
+ s.homepage = "http://www.viget.com/extend/simple-apis-using-serializewithoptions"
12
+ s.summary = %q{Simple XML and JSON APIs for your Rails app}
13
+ s.description = %q{Simple XML and JSON APIs for your Rails app}
14
+
15
+ s.rubyforge_project = "serialize_with_options"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_dependency "active_record", "~> 2.3"
23
+
24
+ s.add_development_dependency "shoulda"
25
+ s.add_development_dependency "sqlite3"
26
+ end
@@ -0,0 +1,215 @@
1
+ require 'test_helper'
2
+
3
+ class User < ActiveRecord::Base
4
+ has_many :posts
5
+ has_many :blog_posts
6
+ has_many :check_ins
7
+ has_many :reviews, :as => :reviewable
8
+
9
+ serialize_with_options do
10
+ methods :post_count
11
+ includes :posts
12
+ except :email
13
+ end
14
+
15
+ serialize_with_options(:with_email) do
16
+ methods :post_count
17
+ includes :posts
18
+ end
19
+
20
+ serialize_with_options(:with_comments) do
21
+ includes :posts => { :include => :comments }
22
+ end
23
+
24
+ serialize_with_options(:with_check_ins) do
25
+ includes :check_ins
26
+ dasherize false
27
+ skip_types true
28
+ end
29
+
30
+ serialize_with_options(:with_reviews) do
31
+ includes :reviews
32
+ end
33
+
34
+ def post_count
35
+ self.posts.count
36
+ end
37
+ end
38
+
39
+ class Post < ActiveRecord::Base
40
+ belongs_to :user
41
+ has_many :comments
42
+ has_many :reviews, :as => :reviewable
43
+
44
+ serialize_with_options do
45
+ only :title
46
+ includes :user, :comments
47
+ end
48
+
49
+ serialize_with_options(:with_email) do
50
+ includes :user, :comments
51
+ end
52
+ end
53
+
54
+ class BlogPost < Post
55
+ serialize_with_options(:with_email) do
56
+ includes :user
57
+ end
58
+ end
59
+
60
+ class Comment < ActiveRecord::Base
61
+ belongs_to :post
62
+ end
63
+
64
+ class CheckIn < ActiveRecord::Base
65
+ belongs_to :user
66
+
67
+ serialize_with_options do
68
+ only :code_name
69
+ includes :user
70
+ end
71
+ end
72
+
73
+ class Review < ActiveRecord::Base
74
+ belongs_to :reviewable, :polymorphic => true
75
+
76
+ serialize_with_options do
77
+ includes :reviewable
78
+ end
79
+ end
80
+
81
+ class SerializeWithOptionsTest < Test::Unit::TestCase
82
+ def self.should_serialize_with_options
83
+ should "include active_record attributes" do
84
+ assert_equal @user.name, @user_hash["name"]
85
+ end
86
+
87
+ should "include specified methods" do
88
+ assert_equal @user.post_count, @user_hash["post_count"]
89
+ end
90
+
91
+ should "exclude specified attributes" do
92
+ assert_equal nil, @user_hash["email"]
93
+ end
94
+
95
+ should "exclude attributes not in :only list" do
96
+ assert_equal nil, @post_hash["content"]
97
+ end
98
+
99
+ should "include specified associations" do
100
+ assert_equal @post.title, @user_hash["posts"].first["title"]
101
+ end
102
+
103
+ should "be identical in inherited model" do
104
+ assert_equal @post_hash["title"], @blog_post_hash["title"]
105
+ end
106
+
107
+ should "include specified methods on associations" do
108
+ assert_equal @user.post_count, @post_hash["user"]["post_count"]
109
+ end
110
+
111
+ should "exclude specified methods on associations" do
112
+ assert_equal nil, @post_hash["user"]["email"]
113
+ end
114
+
115
+ should "not include associations of associations" do
116
+ assert_equal nil, @user_hash["posts"].first["comments"]
117
+ end
118
+
119
+ should "include association without serialization options properly" do
120
+ assert_equal @comment.content, @post_hash["comments"].first["content"]
121
+ end
122
+
123
+ should "override sets on inherited models" do
124
+ assert_equal nil, @blog_post_hash["comments"].first
125
+ end
126
+ end
127
+
128
+ context "An instance of a class with serialization options" do
129
+ setup do
130
+ @user = User.create(:name => "John User", :email => "john@example.com")
131
+ @post = @user.posts.create(:title => "Hello World!", :content => "Welcome to my blog.")
132
+ @blog_post = @user.blog_posts.create(:title => "Hello World!", :content => "Welcome to my blog.")
133
+ @comment = @post.comments.create(:content => "Great blog!")
134
+ end
135
+
136
+ context "being converted to XML" do
137
+ setup do
138
+ @user_hash = Hash.from_xml(@user.to_xml)["user"]
139
+ @post_hash = Hash.from_xml(@post.to_xml)["post"]
140
+ @blog_post_hash = Hash.from_xml(@blog_post.to_xml)["blog_post"]
141
+ end
142
+
143
+ should_serialize_with_options
144
+ end
145
+
146
+
147
+ should "accept additional properties w/o overwriting defaults" do
148
+ xml = @post.to_xml(:include => { :user => { :except => nil } })
149
+ post_hash = Hash.from_xml(xml)["post"]
150
+
151
+ assert_equal @user.email, post_hash["user"]["email"]
152
+ assert_equal @user.post_count, post_hash["user"]["post_count"]
153
+ end
154
+
155
+ should "accept a hash for includes directive" do
156
+ user_hash = Hash.from_xml(@user.to_xml(:with_comments))["user"]
157
+ assert_equal @comment.content, user_hash["posts"].first["comments"].first["content"]
158
+ end
159
+
160
+ context "with a secondary configuration" do
161
+ should "use it" do
162
+ user_hash = Hash.from_xml(@user.to_xml(:with_email))["user"]
163
+ assert_equal @user.email, user_hash["email"]
164
+ end
165
+
166
+ should "pass it through to included models" do
167
+ post_hash = Hash.from_xml(@post.to_xml(:with_email))["post"]
168
+ assert_equal @user.email, post_hash["user"]["email"]
169
+ end
170
+ end
171
+
172
+ context "with a polymorphic relationship" do
173
+ setup do
174
+ @review = Review.create(:reviewable => @user, :content => "troll")
175
+ end
176
+
177
+ should "include the associated object" do
178
+ user_hash = Hash.from_xml(@user.to_xml(:with_reviews))
179
+ assert_equal @review.content, user_hash["user"]["reviews"].first["content"]
180
+ end
181
+
182
+ should "serialize the associated object properly" do
183
+ review_hash = Hash.from_xml(@review.to_xml)
184
+ assert_equal @user.email, review_hash["review"]["reviewable"]["email"]
185
+ end
186
+ end
187
+
188
+ context "being converted to JSON" do
189
+ setup do
190
+ @user_hash = JSON.parse(@user.to_json)
191
+ @post_hash = JSON.parse(@post.to_json)
192
+ @blog_post_hash = JSON.parse(@blog_post.to_json)
193
+ end
194
+
195
+ should_serialize_with_options
196
+ end
197
+
198
+ context "serializing associated models" do
199
+ setup do
200
+ @user = User.create(:name => "John User", :email => "john@example.com")
201
+ @check_in = @user.check_ins.create(:code_name => "Hello World")
202
+ end
203
+
204
+ should "find associations with multi-word names" do
205
+ user_hash = JSON.parse(@user.to_json(:with_check_ins))
206
+ assert_equal @check_in.code_name, user_hash['check_ins'].first['code_name']
207
+ end
208
+
209
+ should "respect xml formatting options" do
210
+ assert !@user.to_xml(:with_check_ins).include?('check-ins')
211
+ assert !@user.to_xml(:with_check_ins).include?('type=')
212
+ end
213
+ end
214
+ end
215
+ end
@@ -0,0 +1,45 @@
1
+ $:.unshift(File.dirname(__FILE__) + '/..')
2
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
3
+
4
+ require 'rubygems'
5
+ require 'active_record'
6
+ require 'test/unit'
7
+ require 'shoulda'
8
+ require 'json'
9
+ require 'serialize_with_options'
10
+ require 'init'
11
+
12
+ ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
13
+
14
+ [:users, :posts, :comments, :check_ins, :reviews].each do |table|
15
+ ActiveRecord::Base.connection.drop_table table rescue nil
16
+ end
17
+
18
+ ActiveRecord::Base.connection.create_table :users do |t|
19
+ t.string :name
20
+ t.string :email
21
+ end
22
+
23
+ ActiveRecord::Base.connection.create_table :posts do |t|
24
+ t.string :title
25
+ t.text :content
26
+ t.integer :user_id
27
+ t.string :type
28
+ end
29
+
30
+ ActiveRecord::Base.connection.create_table :comments do |t|
31
+ t.text :content
32
+ t.integer :post_id
33
+ end
34
+
35
+ ActiveRecord::Base.connection.create_table :check_ins do |t|
36
+ t.integer :user_id
37
+ t.string :code_name
38
+ end
39
+
40
+ ActiveRecord::Base.connection.create_table :reviews do |t|
41
+ t.string :content
42
+ t.integer :reviewable_id
43
+ t.string :reviewable_type
44
+ end
45
+
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: serialize_with_options
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - David Eisinger
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-10-10 00:00:00.000000000 -04:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: active_record
17
+ requirement: &2153256800 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ~>
21
+ - !ruby/object:Gem::Version
22
+ version: '2.3'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: *2153256800
26
+ - !ruby/object:Gem::Dependency
27
+ name: shoulda
28
+ requirement: &2153256380 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: *2153256380
37
+ - !ruby/object:Gem::Dependency
38
+ name: sqlite3
39
+ requirement: &2153255920 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ type: :development
46
+ prerelease: false
47
+ version_requirements: *2153255920
48
+ description: Simple XML and JSON APIs for your Rails app
49
+ email:
50
+ - david.eisinger@gmail.com
51
+ executables: []
52
+ extensions: []
53
+ extra_rdoc_files: []
54
+ files:
55
+ - .gitignore
56
+ - Gemfile
57
+ - MIT-LICENSE
58
+ - README.markdown
59
+ - Rakefile
60
+ - init.rb
61
+ - lib/serialize_with_options.rb
62
+ - lib/serialize_with_options/version.rb
63
+ - serialize_with_options.gemspec
64
+ - test/serialize_with_options_test.rb
65
+ - test/test_helper.rb
66
+ has_rdoc: true
67
+ homepage: http://www.viget.com/extend/simple-apis-using-serializewithoptions
68
+ licenses: []
69
+ post_install_message:
70
+ rdoc_options: []
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
+ none: false
81
+ requirements:
82
+ - - ! '>='
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ requirements: []
86
+ rubyforge_project: serialize_with_options
87
+ rubygems_version: 1.6.2
88
+ signing_key:
89
+ specification_version: 3
90
+ summary: Simple XML and JSON APIs for your Rails app
91
+ test_files:
92
+ - test/serialize_with_options_test.rb
93
+ - test/test_helper.rb