rack-core-data 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :rubygems
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,38 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ rack-core-data (0.0.1)
5
+ activesupport (~> 3.2.6)
6
+ nokogiri (~> 1.4)
7
+ rack (~> 1.4)
8
+ sequel (~> 3.37.0)
9
+ sinatra (~> 1.3.2)
10
+
11
+ GEM
12
+ remote: http://rubygems.org/
13
+ specs:
14
+ activesupport (3.2.6)
15
+ i18n (~> 0.6)
16
+ multi_json (~> 1.0)
17
+ i18n (0.6.0)
18
+ multi_json (1.3.6)
19
+ nokogiri (1.5.5)
20
+ rack (1.4.1)
21
+ rack-protection (1.2.0)
22
+ rack
23
+ rake (0.9.2.2)
24
+ rspec (0.6.4)
25
+ sequel (3.37.0)
26
+ sinatra (1.3.2)
27
+ rack (~> 1.3, >= 1.3.6)
28
+ rack-protection (~> 1.2)
29
+ tilt (~> 1.3, >= 1.3.3)
30
+ tilt (1.3.3)
31
+
32
+ PLATFORMS
33
+ ruby
34
+
35
+ DEPENDENCIES
36
+ rack-core-data!
37
+ rake (~> 0.9.2)
38
+ rspec (~> 0.6.1)
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2012 Mattt Thompson (http://mattt.me/)
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # Rack::CoreData
2
+ **Automatically generate REST APIs for Core Data models**
3
+
4
+ > This is still in early stages of development, so proceed with caution when using this in a production application. Any bug reports, feature requests, or general feedback at this point would be greatly appreciated.
5
+
6
+ Building web services for iOS apps is a constant struggle to coordinating data models. You're _probably_ not running Objective-C on the server, so you're stuck duplicating your business logic--allthewhile doing your best to maintain the correct conventions and idioms for each platform.
7
+
8
+ `Rack::CoreData` aims to bridge the client/server divide, and save you time.
9
+
10
+ Simply point `Rack::CoreData` at your Core Data model file, and a RESTful webserive is automatically created for you, with all of the resource endpoints you might expect in Rails.
11
+
12
+ Since we're running on Rack, each endpoint can be overriden if you need to add or change any existing behavior. Likewise, any of the models in the application can be re-opened to make any necessary adjustments.
13
+
14
+ Think of it like an API scaffold: while you may well throw all of it away eventually, having something to start with will allow you to iterate on the most important parts of your application while you're the most excited about them.
15
+
16
+ ## Usage
17
+
18
+ ### Gemfile
19
+
20
+ ```Ruby
21
+ $ gem 'rack-core-data', :require => 'rack/core-data'
22
+ ```
23
+
24
+ ### config.ru
25
+
26
+ ```ruby
27
+ require 'bundler'
28
+ Bundler.require
29
+
30
+ # Rack::CoreData requires a Sequel connection to a database
31
+ DB = Sequel.connect(ENV['DATABASE_URL'] || "postgres://localhost:5432/coredata")
32
+
33
+ run Rack::CoreData('./Example.xcdatamodeld')
34
+ ```
35
+
36
+ ## Examples
37
+
38
+ An example web API using a Core Data model can be found the `/example` directory.
39
+
40
+ It uses the same data model as the [AFIncrementalStore](https://github.com/afnetworking/afincrementalStore/) example iOS project, so try running that against Rack::CoreData running on localhost.
41
+
42
+ ## Contact
43
+
44
+ Mattt Thompson
45
+
46
+ - http://github.com/mattt
47
+ - http://twitter.com/mattt
48
+ - m@mattt.me
49
+
50
+ ## License
51
+
52
+ Rack::CoreData is available under the MIT license. See the LICENSE file for more info.
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler"
2
+ Bundler.setup
3
+
4
+ gemspec = eval(File.read("rack-core-data.gemspec"))
5
+
6
+ task :build => "#{gemspec.full_name}.gem"
7
+
8
+ file "#{gemspec.full_name}.gem" => gemspec.files + ["rack-core-data.gemspec"] do
9
+ system "gem build rack-core-data.gemspec"
10
+ end
@@ -0,0 +1,33 @@
1
+ class Rack::CoreData::DataModel
2
+ class Attribute
3
+ attr_reader :name, :type, :identifier, :version_hash_modifier, :default_value
4
+
5
+ def initialize(attribute)
6
+ raise ArgumentError unless ::Nokogiri::XML::Element === attribute
7
+
8
+ @name = attribute['name']
9
+ @type = attribute['attributeType']
10
+ @identifier = attribute['elementID']
11
+ @version_hash_modifier = attribute['versionHashModifier']
12
+ @default_value = case @type
13
+ when "Integer 16", "Integer 32", "Integer 64"
14
+ attribute['defaultValueString'].to_i
15
+ when "Float", "Decimal"
16
+ attribute['defaultValueString'].to_f
17
+ end if attribute['defaultValueString']
18
+
19
+ @optional = attribute['optional'] == "YES"
20
+ @transient = attribute['transient'] == "YES"
21
+ @indexed = attribute['indexed'] == "YES"
22
+ @syncable = attribute['syncable'] == "YES"
23
+ end
24
+
25
+ def to_s
26
+ @name
27
+ end
28
+
29
+ [:optional, :transient, :indexed, :syncable].each do |symbol|
30
+ define_method("#{symbol}?") {!!instance_variable_get(("@#{symbol}").intern)}
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,17 @@
1
+ class Rack::CoreData::DataModel
2
+ class Entity
3
+ attr_reader :name, :attributes, :relationships
4
+
5
+ def initialize(entity)
6
+ raise ArgumentError unless ::Nokogiri::XML::Element === entity
7
+
8
+ @name = entity['name']
9
+ @attributes = entity.xpath('attribute').collect{|element| Attribute.new(element)}
10
+ @relationships = entity.xpath('relationship').collect{|element| Relationship.new(element)}
11
+ end
12
+
13
+ def to_s
14
+ @name
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,37 @@
1
+ class Rack::CoreData::DataModel
2
+ class Relationship
3
+ attr_reader :name, :destination, :inverse, :deletion_rule, :min_count, :max_count
4
+
5
+ def initialize(relationship)
6
+ raise ArgumentError unless ::Nokogiri::XML::Element === relationship
7
+
8
+ @name = relationship['name']
9
+ @destination = relationship['destinationEntity']
10
+ @inverse = relationship['inverseName']
11
+ @deletion_rule = relationship['deletionRule'].downcase.to_sym
12
+
13
+ @min_count = relationship['minCount'].to_i
14
+ @max_count = relationship['maxCount'].to_i
15
+
16
+ @to_many = relationship['toMany'] == "YES"
17
+ @optional = relationship['optional'] == "YES"
18
+ @syncable = relationship['syncable'] == "YES"
19
+ end
20
+
21
+ def to_s
22
+ @name
23
+ end
24
+
25
+ def to_many?
26
+ !!@to_many
27
+ end
28
+
29
+ def to_one?
30
+ !to_many?
31
+ end
32
+
33
+ [:optional, :syncable].each do |symbol|
34
+ define_method("#{symbol}?") {!!instance_variable_get(("@#{symbol}").intern)}
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,36 @@
1
+ require 'nokogiri'
2
+
3
+ module Rack::CoreData
4
+ class DataModel
5
+ attr_reader :name, :version, :entities
6
+
7
+ def initialize(data_model)
8
+ loop do
9
+ case data_model
10
+ when File, /^\<\?xml/
11
+ data_model = ::Nokogiri::XML(data_model) and redo
12
+ when String
13
+ case data_model
14
+ when /\.xcdatamodeld?$/
15
+ data_model = Dir[File.join(data_model, "/**/contents")].first and redo
16
+ else
17
+ data_model = ::File.read(data_model) and redo
18
+ end
19
+ when ::Nokogiri::XML::Document
20
+ break
21
+ else
22
+ raise ArgumentError
23
+ end
24
+ end
25
+
26
+ model = data_model.at_xpath('model')
27
+ @name = model['name']
28
+ @version = model['systemVersion']
29
+ @entities = model.xpath('entity').collect{|element| Entity.new(element)}
30
+ end
31
+ end
32
+ end
33
+
34
+ require 'rack/core-data/data_model/entity'
35
+ require 'rack/core-data/data_model/attribute'
36
+ require 'rack/core-data/data_model/relationship'
@@ -0,0 +1,5 @@
1
+ module Rack
2
+ module CoreData
3
+ VERSION = '0.0.1'
4
+ end
5
+ end
@@ -0,0 +1,147 @@
1
+ require 'rack'
2
+ require 'sinatra/base'
3
+
4
+ require 'sequel'
5
+ require 'active_support/inflector'
6
+
7
+ require 'rack/core-data/data_model'
8
+ require 'rack/core-data/version'
9
+
10
+ module Rack::CoreData::Models
11
+ end
12
+
13
+ module Rack
14
+ def self.CoreData(xcdatamodel)
15
+ app = Class.new(Sinatra::Base) do
16
+ before do
17
+ content_type :json
18
+ end
19
+ end
20
+
21
+ model = CoreData::DataModel.new(xcdatamodel)
22
+
23
+ # Create each model class before implementing, in order to correctly set up relationships
24
+ model.entities.each do |entity|
25
+ klass = Rack::CoreData::Models.const_set(entity.name.capitalize, Class.new(Sequel::Model))
26
+ end
27
+
28
+ model.entities.each do |entity|
29
+ klass = Rack::CoreData::Models.const_get(entity.name.capitalize)
30
+ klass.dataset = entity.name.downcase.pluralize.to_sym
31
+
32
+ klass.class_eval do
33
+ strict_param_setting = false
34
+ plugin :json_serializer, :naked => true, :include => :url, :except => :id
35
+ plugin :schema
36
+
37
+ def url
38
+ "/#{self.class.table_name}/#{id}"
39
+ end
40
+
41
+ entity.relationships.each do |relationship|
42
+ options = {:class => Rack::CoreData::Models.const_get(relationship.destination.capitalize)}
43
+
44
+ if relationship.to_many?
45
+ one_to_many relationship.name.to_sym, options
46
+ else
47
+ many_to_one relationship.name.to_sym, options
48
+ end
49
+ end
50
+
51
+ set_schema do
52
+ primary_key :id
53
+
54
+ entity.attributes.each do |attribute|
55
+ options = {
56
+ :null => attribute.optional?,
57
+ :index => attribute.indexed?,
58
+ :default => attribute.default_value
59
+ }
60
+
61
+ type = case attribute.type
62
+ when "Integer 16" then :int2
63
+ when "Integer 32" then :int4
64
+ when "Integer 64" then :int8
65
+ when "Float" then :float4
66
+ when "Decimal" then :float8
67
+ when "Date" then :timestamp
68
+ when "Boolean" then :boolean
69
+ when "Binary" then :bytea
70
+ else :varchar
71
+ end
72
+
73
+ column attribute.name, type, options
74
+ end
75
+
76
+ entity.relationships.each do |relationship|
77
+ options = {
78
+ :index => true,
79
+ :null => relationship.optional?
80
+ }
81
+
82
+ if not relationship.to_many?
83
+ column "#{relationship.name}_id".to_sym, :integer, options
84
+ end
85
+ end
86
+ end
87
+
88
+ create_table unless table_exists?
89
+ end
90
+
91
+ app.class_eval do
92
+ include Rack::CoreData::Models
93
+ klass = Rack::CoreData::Models.const_get(entity.name.capitalize)
94
+
95
+ get "/#{entity.name.downcase.pluralize}/?" do
96
+ klass.all.to_json
97
+ end
98
+
99
+ post "/#{entity.name.downcase.pluralize}/?" do
100
+ record = klass.new(params)
101
+ if record.save
102
+ status 201
103
+ record.to_json
104
+ else
105
+ status 406
106
+ record.errors.to_json
107
+ end
108
+ end
109
+
110
+ get "/#{entity.name.downcase.pluralize}/:id/?" do
111
+ klass[params[:id]].to_json
112
+ end
113
+
114
+ put "/#{entity.name.downcase.pluralize}/:id/?" do
115
+ record = klass[params[:id]] or halt 404
116
+ if record.update(params)
117
+ status 200
118
+ record.to_json
119
+ else
120
+ status 406
121
+ record.errors.to_json
122
+ end
123
+ end
124
+
125
+ delete "/#{entity.name.downcase.pluralize}/:id/?" do
126
+ record = klass[params[:id]] or halt 404
127
+ if record.destroy
128
+ status 200
129
+ else
130
+ status 406
131
+ record.errors.to_json
132
+ end
133
+ end
134
+
135
+ entity.relationships.each do |relationship|
136
+ next unless relationship.to_many?
137
+
138
+ get "/#{entity.name.downcase.pluralize}/:id/#{relationship.name}/?" do
139
+ klass[params[:id]].send(relationship.name).to_json
140
+ end
141
+ end
142
+ end
143
+ end
144
+
145
+ return app
146
+ end
147
+ end
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "rack/core-data"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "rack-core-data"
7
+ s.authors = ["Mattt Thompson"]
8
+ s.email = "m@mattt.me"
9
+ s.homepage = "http://mattt.me"
10
+ s.version = Rack::CoreData::VERSION
11
+ s.platform = Gem::Platform::RUBY
12
+ s.summary = "Rack::CoreData"
13
+ s.description = "Automatically generate REST APIs for Core Data models."
14
+
15
+ s.add_development_dependency "rspec", "~> 0.6.1"
16
+ s.add_development_dependency "rake", "~> 0.9.2"
17
+
18
+ s.add_dependency "rack", "~> 1.4"
19
+ s.add_dependency "nokogiri", "~> 1.4"
20
+ s.add_dependency "sinatra", "~> 1.3.2"
21
+ s.add_dependency "sequel", "~> 3.37.0"
22
+ s.add_dependency "activesupport", "~> 3.2.6"
23
+
24
+ s.files = Dir["./**/*"].reject { |file| file =~ /\.\/(bin|example|log|pkg|script|spec|test|vendor)/ }
25
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
26
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
27
+ s.require_paths = ["lib"]
28
+ end
metadata ADDED
@@ -0,0 +1,139 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-core-data
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Mattt Thompson
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-07-24 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: &70340923946660 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 0.6.1
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *70340923946660
25
+ - !ruby/object:Gem::Dependency
26
+ name: rake
27
+ requirement: &70340923946160 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: 0.9.2
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *70340923946160
36
+ - !ruby/object:Gem::Dependency
37
+ name: rack
38
+ requirement: &70340923945700 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: '1.4'
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *70340923945700
47
+ - !ruby/object:Gem::Dependency
48
+ name: nokogiri
49
+ requirement: &70340923945240 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '1.4'
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: *70340923945240
58
+ - !ruby/object:Gem::Dependency
59
+ name: sinatra
60
+ requirement: &70340923944780 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ~>
64
+ - !ruby/object:Gem::Version
65
+ version: 1.3.2
66
+ type: :runtime
67
+ prerelease: false
68
+ version_requirements: *70340923944780
69
+ - !ruby/object:Gem::Dependency
70
+ name: sequel
71
+ requirement: &70340923944320 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ~>
75
+ - !ruby/object:Gem::Version
76
+ version: 3.37.0
77
+ type: :runtime
78
+ prerelease: false
79
+ version_requirements: *70340923944320
80
+ - !ruby/object:Gem::Dependency
81
+ name: activesupport
82
+ requirement: &70340923734700 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ~>
86
+ - !ruby/object:Gem::Version
87
+ version: 3.2.6
88
+ type: :runtime
89
+ prerelease: false
90
+ version_requirements: *70340923734700
91
+ description: Automatically generate REST APIs for Core Data models.
92
+ email: m@mattt.me
93
+ executables: []
94
+ extensions: []
95
+ extra_rdoc_files: []
96
+ files:
97
+ - ./Gemfile
98
+ - ./Gemfile.lock
99
+ - ./lib/rack/core-data/data_model/attribute.rb
100
+ - ./lib/rack/core-data/data_model/entity.rb
101
+ - ./lib/rack/core-data/data_model/relationship.rb
102
+ - ./lib/rack/core-data/data_model.rb
103
+ - ./lib/rack/core-data/version.rb
104
+ - ./lib/rack/core-data.rb
105
+ - ./LICENSE
106
+ - ./rack-core-data.gemspec
107
+ - ./Rakefile
108
+ - ./README.md
109
+ homepage: http://mattt.me
110
+ licenses: []
111
+ post_install_message:
112
+ rdoc_options: []
113
+ require_paths:
114
+ - lib
115
+ required_ruby_version: !ruby/object:Gem::Requirement
116
+ none: false
117
+ requirements:
118
+ - - ! '>='
119
+ - !ruby/object:Gem::Version
120
+ version: '0'
121
+ segments:
122
+ - 0
123
+ hash: 3292093600849607730
124
+ required_rubygems_version: !ruby/object:Gem::Requirement
125
+ none: false
126
+ requirements:
127
+ - - ! '>='
128
+ - !ruby/object:Gem::Version
129
+ version: '0'
130
+ segments:
131
+ - 0
132
+ hash: 3292093600849607730
133
+ requirements: []
134
+ rubyforge_project:
135
+ rubygems_version: 1.8.15
136
+ signing_key:
137
+ specification_version: 3
138
+ summary: Rack::CoreData
139
+ test_files: []