motion-json-decoder 0.0.1

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.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