motion-json-decoder 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,82 @@
1
+ motion-json-decoder
2
+ ===================
3
+
4
+ Turn JSON nodes into proper Ruby objects.
5
+
6
+ Installation
7
+ ------------
8
+ * Add `motion-json-decoder` to your Gemfile and run `bundle`.
9
+ * Add `require 'motion-json-decoder` to your Rakefile.
10
+
11
+ Basic Example
12
+ -------------
13
+
14
+ Let's say you have a RubyMotion app which parses this response from the server:
15
+
16
+ {
17
+ "people":
18
+ [
19
+ {
20
+ "first_name": "John",
21
+ "last_name": "Smith",
22
+ "date_of_birth": "1980-01-03"
23
+ },
24
+ {
25
+ "first_name": "Joe",
26
+ "last_name": "Bloggs",
27
+ "date_of_birth": "1967-10-11"
28
+ }
29
+ ]
30
+ }
31
+
32
+ So that's a *people* collection which contains two *person* nodes.
33
+
34
+ There may be a number of place in your app where you want to display a person's full name:
35
+
36
+ names = []
37
+ json['people'].each do |person|
38
+ full_name = person['first_name'] + ' ' + person['last_name']
39
+ names << full_name
40
+ end
41
+
42
+ But doing this in multiply places isn't DRY. You could write a helper method, but it's hard to think where
43
+ that should live.
44
+
45
+ *motion-json-decoder* allows the creating of a mapping between your JSON nodes and simple objects. Just include the module in your class:
46
+
47
+ class Person
48
+ include JSONDecoder::Node
49
+
50
+ field :first_name
51
+ field :last_name
52
+
53
+ def full_name
54
+ first_name + ' ' + last_name
55
+ end
56
+ end
57
+
58
+ You can then treat person as a proper object:
59
+
60
+ names = []
61
+ json['people'].each do |person_node|
62
+ person = Person.new person_node
63
+ names << person.full_name
64
+ end
65
+
66
+ Typo Catching
67
+ -------------
68
+
69
+ Under the hood, motion-json-decoder uses Hash#fetch rather than Hash#[], so if you call a field which doesn't exist, you'll get an exception right away, rather than a potentially difficult-to-debug nil return value.
70
+
71
+ Collections
72
+ ------------
73
+
74
+ You can specify that a nodes contains a collection of other 'resources' rather than a simple literals:
75
+
76
+ collection :organisations, :class_name -> { Organisation }
77
+
78
+ class_name parameter should be another class which includes the `JSONDecoder::Node` module.
79
+
80
+ When you call json.people, rather than array of hashes, you'll get an array of Organisation objects.
81
+
82
+ The use of the lambda (->) is to avoid dependency resolution problems at compile time.
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,15 @@
1
+ unless ENV['SKIP_RM_CHECK']
2
+ unless defined?(Motion::Project::Config)
3
+ raise "This file must be required within a RubyMotion project Rakefile."
4
+ end
5
+
6
+ Motion::Project::App.setup do |app|
7
+ Dir.glob(File.join(File.dirname(__FILE__), 'motion_json_decoder/*.rb')).each do |file|
8
+ app.files.unshift(file)
9
+ end
10
+ end
11
+ end
12
+
13
+ require 'motion_json_decoder/version'
14
+ require 'motion_json_decoder/node'
15
+ require 'motion_json_decoder/date_parser'
@@ -0,0 +1,14 @@
1
+ module JSONDecoder
2
+ class DateParser
3
+ def initialize(format = "yyyy-MM-dd")
4
+ @format = format
5
+ end
6
+
7
+ def parse(date_string)
8
+ date_formatter = NSDateFormatter.alloc.init
9
+ date_formatter.setDateFormat @format
10
+ result = date_formatter.dateFromString date_string
11
+ result or raise "Failed to parse date '#{date_string}' using format '#{format}'"
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,49 @@
1
+
2
+ module JSONDecoder
3
+ module Node
4
+ def initialize(json)
5
+ self.json = json
6
+ end
7
+
8
+ def self.included(base)
9
+ base.send :extend, ClassMethods
10
+ end
11
+
12
+ module ClassMethods
13
+ def field(field_name, opts = {})
14
+ type = opts[:type]
15
+ klass = opts[:using]
16
+ raise "Cannot use :type and :using together" if type && klass
17
+ method_name = opts[:as] || field_name
18
+ define_method method_name do
19
+ result = json[field_name.to_s]
20
+ if type == :date
21
+ JSONDecoder::DateParser.new.parse(result) # TODO move this into lib if gemifying
22
+ elsif klass
23
+ klass.new result
24
+ else
25
+ result
26
+ end
27
+ end
28
+ end
29
+
30
+ def collection(name, opts = {})
31
+ klass = opts.fetch(:class_name)
32
+ define_method name do
33
+ # Using a block to avoid dependency reolutions problems.
34
+ lambda do
35
+ json.fetch(name.to_s).map { |element| klass.call.new(element) }
36
+ end.call
37
+ end
38
+ end
39
+ end
40
+
41
+ def links
42
+ json[:_links]
43
+ end
44
+
45
+ private
46
+
47
+ attr_accessor :json
48
+ end
49
+ end
@@ -0,0 +1,5 @@
1
+ module Motion
2
+ module JSONDecoder
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,17 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/motion_json_decoder/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.name = "motion-json-decoder"
6
+ gem.authors = ["Andy Waite"]
7
+ gem.email = ["andy@andywaite.com"]
8
+ gem.description = "Turn JSON nodes into rich Ruby objects"
9
+ gem.summary = "JSON decoder for RubyMotion"
10
+ gem.homepage = "https://github.com/andyw8/motion-json-decoder"
11
+
12
+ gem.files = `git ls-files`.split($\)
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.require_paths = ["lib"]
15
+ gem.version = Motion::JSONDecoder::VERSION
16
+ gem.add_development_dependency 'rspec'
17
+ end
@@ -0,0 +1,47 @@
1
+ require 'motion_json_decoder'
2
+
3
+ describe "JSONDecoder::Node" do
4
+ it "exposes fields" do
5
+ class Foo
6
+ include JSONDecoder::Node
7
+ field :first_name
8
+ end
9
+
10
+ json_hash = {'first_name' => 'Andy'}
11
+ node = Foo.new json_hash
12
+ node.first_name.should == 'Andy'
13
+ end
14
+
15
+ it "allows fields to be aliased" do
16
+ class Foo
17
+ include JSONDecoder::Node
18
+ field :first_name, as: 'forename'
19
+ end
20
+
21
+ json_hash = {'first_name' => 'Andy'}
22
+ node = Foo.new json_hash
23
+ node.forename.should == 'Andy'
24
+ end
25
+
26
+ it "handles collections" do
27
+ class Organisation
28
+ include JSONDecoder::Node
29
+ field :name
30
+ end
31
+
32
+ class Foo
33
+ include JSONDecoder::Node
34
+ collection :organisations, class_name: -> { Organisation }
35
+ end
36
+
37
+ json_hash = {
38
+ 'organisations' =>
39
+ [
40
+ {'name' => 'Reddit'},
41
+ {'name' => 'Hacker News'}
42
+ ]
43
+ }
44
+ node = Foo.new json_hash
45
+ node.organisations.map(&:name).should == ['Reddit', 'Hacker News']
46
+ end
47
+ end
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: motion-json-decoder
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Andy Waite
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-02-21 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ description: Turn JSON nodes into rich Ruby objects
31
+ email:
32
+ - andy@andywaite.com
33
+ executables: []
34
+ extensions: []
35
+ extra_rdoc_files: []
36
+ files:
37
+ - README.md
38
+ - Rakefile
39
+ - lib/motion-json-decoder.rb
40
+ - lib/motion_json_decoder/date_parser.rb
41
+ - lib/motion_json_decoder/node.rb
42
+ - lib/motion_json_decoder/version.rb
43
+ - motion-json-decoder.gemspec
44
+ - spec/motion_json_decoder/node_spec.rb
45
+ homepage: https://github.com/andyw8/motion-json-decoder
46
+ licenses: []
47
+ post_install_message:
48
+ rdoc_options: []
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ! '>='
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ requirements: []
64
+ rubyforge_project:
65
+ rubygems_version: 1.8.23
66
+ signing_key:
67
+ specification_version: 3
68
+ summary: JSON decoder for RubyMotion
69
+ test_files:
70
+ - spec/motion_json_decoder/node_spec.rb