yogo-db 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../../lib')
2
+ require 'yogo-db'
@@ -0,0 +1,9 @@
1
+ Feature: something something
2
+ In order to something something
3
+ A user something something
4
+ something something something
5
+
6
+ Scenario: something something
7
+ Given inspiration
8
+ When I create a sweet new gem
9
+ Then everyone should see how awesome I am
@@ -0,0 +1,4 @@
1
+ # Load Application pieces
2
+ require 'yogo/rack/model_lookup'
3
+ require 'yogo/schema_app'
4
+ require 'yogo/data_app'
@@ -0,0 +1,89 @@
1
+ module Yogo
2
+ module Data
3
+ module DefaultMethods
4
+
5
+ def self.included(base)
6
+ base.send(:extend, ClassMethods)
7
+ base.send(:include, InstanceMethods)
8
+ end
9
+
10
+ module ClassMethods
11
+ def schema
12
+ class_variable_get(:@@schema)
13
+ end
14
+
15
+ def parse_json(body)
16
+ json = JSON.parse(body)
17
+ props = labeled_properties
18
+ ret = {}
19
+ # I don't think we care about other items passed back.
20
+ json['data'].each_pair do |key,value|
21
+ property = props.select{|p| p.options['label'] == key || p.name.to_s == key}
22
+ return nil unless property.length == 1
23
+ # raise Exception, "There shouldn't be more the one property with the same label"
24
+ ret[property.first.name] = value
25
+ end
26
+ return ret
27
+ end
28
+
29
+ def labeled_properties
30
+ properties.select{|p| !p.options['label'].nil? }
31
+ end
32
+
33
+ def unlabeled_properties
34
+ properties.select{|p| p.options['label'].nil? }
35
+ end
36
+ end
37
+
38
+ module InstanceMethods
39
+ def to_url
40
+ raise Exception, "Need to save before item has a url" if new?
41
+ "/data/" << self.model.name << "/" << yogo_id.to_s
42
+ end
43
+
44
+ def attributes_by_label
45
+ attributes = {}
46
+ properties.each do |property|
47
+ label = property.options['label']
48
+ next if label.nil?
49
+ name = property.name
50
+ next unless model.public_method_defined?(name)
51
+ attributes[label] = __send__(name)
52
+ end
53
+ return attributes
54
+ end
55
+
56
+ def labeled_properties
57
+ self.class.labeled_properties
58
+ end
59
+
60
+ def unlabeled_properties
61
+ self.class.unlabeled_properties
62
+ end
63
+
64
+ def to_json(*args)
65
+ options = args.first || {}
66
+ options = options.to_h if options.respond_to?(:to_h)
67
+
68
+ result = as_json(*args)
69
+
70
+ if options.fetch(:to_json, true)
71
+ result.to_json
72
+ else
73
+ result
74
+ end
75
+ end
76
+
77
+ def as_json(*a)
78
+ data = labeled_properties.reduce({}){ |result, property| result.merge(property.name => __send__(property.name)) }
79
+ default_data = unlabeled_properties.reduce({}){ |result, property| result.merge(property.name => __send__(property.name)) }
80
+ default_data.merge({
81
+ :url => self.to_url,
82
+ :data => data
83
+ })
84
+ end
85
+ end
86
+
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,50 @@
1
+ require 'sinatra'
2
+
3
+ module Yogo
4
+ class DataApp < ::Sinatra::Base
5
+ before do
6
+ content_type :json
7
+ end
8
+
9
+ get '/data/:model_id/?' do
10
+ { :content => env['yogo.resource'].all.to_json(:to_json => false) }.to_json
11
+ end
12
+
13
+ get '/data/:model_id/:id' do
14
+ resource = env['yogo.resource']
15
+ item = resource.get(params[:id])
16
+
17
+ { :content => item }.to_json
18
+ end
19
+
20
+ post '/data/:model_id' do
21
+ resource = env['yogo.resource']
22
+ opts = resource.parse_json(request.body.read) rescue nil
23
+
24
+ halt(401, 'Invalid Format') if opts.nil?
25
+
26
+ item = resource.new(opts)
27
+
28
+ halt(500, 'Could not save item') unless item.save
29
+
30
+ response['Location'] = item.to_url
31
+ response.status = 201
32
+ end
33
+
34
+ put '/schema/:model_id/:id' do
35
+ resource = env['yogo.resource']
36
+ item = resource.get(params[:id])
37
+ opts = resource.parse_json(request.body.read) rescue nil
38
+ halt(401, 'Invalid Format') if opts.nil?
39
+
40
+ halt(500, 'Could not update schema') unless item.update(opts)
41
+
42
+ { :content => item }.to_json
43
+ end
44
+
45
+ delete '/schema/:model_id/:id' do
46
+ env['yogo.resource'].get(params[:id]).destroy
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,138 @@
1
+ require 'dm-types'
2
+ require 'yogo/datamapper/model/configuration'
3
+ require 'yogo/operations'
4
+
5
+ class Schema
6
+ include ::DataMapper::Resource
7
+ # include Yogo::DataMapper::Model::Configuration
8
+ include Dataflow
9
+
10
+ # Common::Properties::UUIDKey[self]
11
+ property :id, ::DataMapper::Property::UUID, {:key => true, :default => lambda{|*args| UUIDTools::UUID.timestamp_create}}
12
+
13
+ property :name, String, :required => true, :key => true, :unique => true
14
+
15
+ property :operation_definitions, Yaml, :lazy => false, :default => []
16
+
17
+ before :save, :reset_data_model
18
+ before :destroy, :destroy_data_model_bucket
19
+
20
+ # How do we know what variables need to be constantized.
21
+ # Also, we might have more or fewer then 3 parameters in the operation
22
+ # def operation_definitions
23
+ # @__operation_definitions ||= attribute_get(:op_defs)
24
+ # end
25
+
26
+ # Set only the operations we want, don't add to them
27
+ def operations=(ops)
28
+ # Clear the current operations
29
+ self.operation_definitions = []
30
+ # Load operations with the set given
31
+ ops.each{|op| operation(*op) }
32
+ end
33
+
34
+ def operation(op_name, *args)
35
+ op_def = replace_nil_items([op_name.to_s, args].flatten)
36
+
37
+ unless self.operation_definitions.include?(op_def)
38
+ # We need to dup this else the model doesn't get marked as dirty and won't save.
39
+ self.operation_definitions = self.operation_definitions.dup << op_def
40
+ end
41
+ end
42
+
43
+ def data_model
44
+ @data_model ||= by_need { gen_model }
45
+ end
46
+
47
+ def to_url
48
+ "/schema/#{self.name}"
49
+ end
50
+
51
+ def to_json(*args)
52
+ options = args.first || {}
53
+ options = options.to_h if options.respond_to?(:to_h)
54
+
55
+ result = as_json(*args)
56
+
57
+ if options.fetch(:to_json, true)
58
+ result.to_json
59
+ else
60
+ result
61
+ end
62
+ end
63
+
64
+ def as_json(*a)
65
+ {
66
+ :guid => self.to_url,
67
+ :name => self.name,
68
+ :operations => self.operation_definitions,
69
+ }
70
+ end
71
+
72
+ REQUIRED_JSON_KEYS=[:name, :operations]
73
+
74
+ def self.parse_json(body)
75
+ json = JSON.parse(body)
76
+
77
+ ret = { :name => json['name'], :operations => json['operations'] }
78
+
79
+ return nil if REQUIRED_JSON_KEYS.any? { |r| ret[r].nil? }
80
+
81
+ ret
82
+ end
83
+
84
+ def to_proc
85
+ base_op = Yogo::DataMapper::Model::Operations['add/default_properties']
86
+ # base_op = Yogo::DataMapper::Model::Operations['add/yogo_methods']
87
+ ops = operation_definitions.map{|op_def| Yogo::DataMapper::Model::Operations[op_def.first] }
88
+ partial_ops = []
89
+ ops.each_with_index do |op, i|
90
+ next unless op
91
+ partial_ops[i] = op.partial(X, *operation_definitions[i][1..-1])
92
+ end
93
+ partial_ops << Yogo::DataMapper::Model::Operations['add/yogo_methods']
94
+ partial_ops.compact!
95
+ partial_ops.reduce(base_op){|composed, partial_op| composed * partial_op}
96
+
97
+ end
98
+
99
+ private
100
+
101
+ def gen_model
102
+ id = attribute_get(:id)
103
+ model_name = attribute_get(:name)
104
+ base_model = ::DataMapper::Model.new do
105
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
106
+
107
+ def self.default_storage_name
108
+ "#{id}"
109
+ end
110
+
111
+ def self.name
112
+ "#{model_name}"
113
+ end
114
+ RUBY
115
+ end
116
+
117
+ base_model.class_variable_set(:@@schema, self)
118
+ self.to_proc[base_model]
119
+ base_model.auto_upgrade!
120
+ return base_model
121
+ end
122
+
123
+ def reset_data_model
124
+ @data_model = nil
125
+ end
126
+
127
+ def destroy_data_model_bucket
128
+ data_model.auto_migrate_down!
129
+ end
130
+
131
+ def generate_column_uuid
132
+ "col_#{UUIDTools::UUID.timestamp_create.to_s.gsub('-', '_')}"
133
+ end
134
+
135
+ def replace_nil_items(array)
136
+ array.map{|i| i.nil? ? generate_column_uuid : i }
137
+ end
138
+ end # Configuration
@@ -0,0 +1,2 @@
1
+ require 'yogo/operations/basic'
2
+ require 'yogo/operations/file'
@@ -0,0 +1,31 @@
1
+ require 'yogo/operation'
2
+ require 'yogo/data/default_methods'
3
+
4
+ # Move into DataMapper::Property extension file
5
+ unless ::DataMapper::Property.accepted_options.include?('label')
6
+ ::DataMapper::Property.accept_options('label')
7
+ end
8
+
9
+ module Operations
10
+ Yogo::DataMapper::Model::Operations['add/yogo_methods'] = Yogo::Op.on(::DataMapper::Model) do |model|
11
+
12
+ # model.define some properties
13
+
14
+ model.send(:include, Yogo::Data::DefaultMethods)
15
+
16
+ model
17
+
18
+ end
19
+
20
+ Yogo::DataMapper::Model::Operations['add/yogo_id_property'] =
21
+ Yogo::DataMapper::Model::Operations['add/property'].partial(X, :yogo_id, 'Serial', {})
22
+ Yogo::DataMapper::Model::Operations['add/created_at_property'] =
23
+ Yogo::DataMapper::Model::Operations['add/property'].partial(X, :created_at, 'DateTime', {})
24
+ Yogo::DataMapper::Model::Operations['add/updated_at_property'] =
25
+ Yogo::DataMapper::Model::Operations['add/property'].partial(X, :updated_at, 'DateTime', {})
26
+
27
+ Yogo::DataMapper::Model::Operations['add/default_properties'] =
28
+ Yogo::DataMapper::Model::Operations['add/yogo_id_property'] *
29
+ Yogo::DataMapper::Model::Operations['add/created_at_property'] *
30
+ Yogo::DataMapper::Model::Operations['add/updated_at_property']
31
+ end
@@ -0,0 +1,44 @@
1
+ # Operation to add a file type here
2
+
3
+ Yogo::DataMapper::Model::Operations['add/file'] = Yogo::Op.on(::DataMapper::Model) do |model, name, options|
4
+
5
+ uploader = Class.new(CarrierWave::Uploader::Base)
6
+ uploader.storage(:file)
7
+ uploader.class_eval %{
8
+ def store_dir
9
+ File.join('assets', '#{model.schema.id}', '#{name}')
10
+ end
11
+
12
+ def filename
13
+ # Digest::MD5.hexdigest(self.read)
14
+ UUIDTools::UUID.timestamp_create
15
+ end
16
+ }, __FILE__, __LINE__+1
17
+
18
+ # model.define some properties
19
+ model.class_eval do
20
+ without_auto_validations do
21
+ # property :content_type, String
22
+ # property :description, String
23
+ property "#{name}_asset_file".to_sym, String, options
24
+ property "#{name}_original_filename".to_sym, String
25
+ end
26
+
27
+ mount_uploader name.to_sym, uploader, :mount_on => "#{name}_asset_file".to_sym
28
+ after "#{name}=".to_sym, "write_#{name}_identifier".to_sym
29
+ after "#{name}=".to_sym, "set_#{name}_original_filename".to_sym
30
+ end
31
+
32
+ model.class_eval %{
33
+ private
34
+
35
+ def set_#{name}_original_filename
36
+ original_filename = #{name}.send(:original_filename)
37
+ attribute_set(:#{name}_original_filename, original_filename)
38
+ end
39
+ }, __FILE__, __LINE__+1
40
+
41
+
42
+ model
43
+
44
+ end
@@ -0,0 +1,37 @@
1
+ # encoding: utf-8
2
+ require 'dm-core'
3
+ require 'yogo/model/schema'
4
+
5
+ module Yogo
6
+ module Rack
7
+ class ModelLookup
8
+ def initialize(app, options = {})
9
+ paths = options[:paths] || ['data']
10
+ @app = app
11
+ @scope = options[:scope] || lambda { |*args| return Schema }
12
+ @base_regexp = /^\/(#{paths.join('|')})\/((\w|-|\s)+)/
13
+ end
14
+
15
+ def call(env)
16
+ if env['PATH_INFO'] =~ @base_regexp
17
+ model_id = $2
18
+ env['yogo.schema'], env['yogo.resource'] = get_model(model_id, env)
19
+ return [ 404, {'Content-Type' => 'text/plain'}, ["#{model_id} not found"] ] if env['yogo.schema'].nil?
20
+ end
21
+
22
+ @app.call(env)
23
+ end
24
+
25
+ private
26
+
27
+ def get_model(model_id, env)
28
+ config = @scope.call(env).first(:name => model_id)
29
+
30
+ unless config.nil?
31
+ return config, config.data_model
32
+ end
33
+ end
34
+
35
+ end # ModelLookup
36
+ end # Rack
37
+ end # Yogo
@@ -0,0 +1,43 @@
1
+ require 'sinatra'
2
+
3
+ module Yogo
4
+ class SchemaApp < ::Sinatra::Base
5
+ before do
6
+ content_type :json
7
+ end
8
+
9
+ get '/schema/?' do
10
+ { :content => Schema.all.to_json(:to_json => false) }.to_json
11
+ end
12
+
13
+ get '/schema/:model_id/?' do
14
+ { :content => env['yogo.schema'] }.to_json
15
+ end
16
+
17
+ post '/schema/?' do
18
+ opts = Schema.parse_json(request.body.read) rescue nil
19
+ halt(401, 'Invalid Format') if opts.nil?
20
+ schema = Schema.new(opts)
21
+
22
+ halt(500, 'Could not save schema') unless schema.save
23
+
24
+ response['Location'] = schema.to_url
25
+ response.status = 201
26
+ end
27
+
28
+ put '/schema/:model_id' do
29
+ schema = env['yogo.schema']
30
+ opts = Schema.parse_json(request.body.read) rescue nil
31
+ halt(401, 'Invalid Format') if opts.nil?
32
+
33
+ halt(500, 'Could not update schema') unless schema.update(opts)
34
+
35
+ { :content => schema }.to_json
36
+ end
37
+
38
+ delete '/schema/:model_id' do
39
+ env['yogo.schema'].destroy
40
+ end
41
+
42
+ end
43
+ end