flexible_api 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/lib/flexible_api/no_such_request_level_error.rb +15 -0
- data/lib/flexible_api/request_level.rb +118 -0
- data/lib/flexible_api/version.rb +5 -0
- data/lib/flexible_api.rb +86 -0
- data/spec/spec_helper.rb +9 -0
- metadata +112 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
module FlexibleApi
|
|
2
|
+
|
|
3
|
+
class RequestLevel
|
|
4
|
+
|
|
5
|
+
def initialize(name, klass)
|
|
6
|
+
@name = name
|
|
7
|
+
@klass = klass
|
|
8
|
+
@display_fields = Set.new
|
|
9
|
+
@select_fields = Set.new
|
|
10
|
+
@includes = []
|
|
11
|
+
@notations = {}
|
|
12
|
+
@eaten_levels = []
|
|
13
|
+
@select_fields << "`#{@klass.table_name}`.#{@klass.primary_key}" # auto-select primary key
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
attr_reader :display_fields, :name, :notations
|
|
17
|
+
|
|
18
|
+
def to_hash
|
|
19
|
+
{
|
|
20
|
+
:name => @name,
|
|
21
|
+
:fields => select_field + notations.keys,
|
|
22
|
+
:includes => @includes.map do |inc|
|
|
23
|
+
{ :name => inc[:name], :type => inc[:association].name.to_s.pluralize.underscore.downcase, :request_level => inc[:request_level].name }
|
|
24
|
+
end
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def eat_level(name)
|
|
29
|
+
@eaten_levels << @klass.find_level(name)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def notation(notation_name, options = {}, &block)
|
|
33
|
+
options.assert_valid_keys :requires
|
|
34
|
+
requires *options[:requires]
|
|
35
|
+
@notations[notation_name] = block
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def includes(association_name, options = {})
|
|
39
|
+
options.assert_valid_keys :request_level, :as, :requires
|
|
40
|
+
association = @klass.reflect_on_all_associations.detect { |a| a.name == association_name.to_sym }
|
|
41
|
+
raise "No such association on #{@klass.name}: #{association_name}" if association.nil? # TODO
|
|
42
|
+
# Allow requires to pass in
|
|
43
|
+
requires *options[:requires] if options.has_key?(:requires)
|
|
44
|
+
# Set the include options
|
|
45
|
+
@includes << {
|
|
46
|
+
:name => options[:as] || association_name,
|
|
47
|
+
:association => association,
|
|
48
|
+
:request_level => association.klass.find_level(options[:request_level])
|
|
49
|
+
}
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def requires(*requires_array)
|
|
53
|
+
requires_array.each do |field|
|
|
54
|
+
if field.is_a?(String)
|
|
55
|
+
@select_fields << field
|
|
56
|
+
else
|
|
57
|
+
@select_fields << "`#{@klass.table_name}`.#{field}" if @klass.columns_hash.keys.include?(field.to_s)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def all_fields
|
|
63
|
+
fields *@klass.columns_hash.keys.map(&:to_sym)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def fields(*field_array)
|
|
67
|
+
field_array.each do |field|
|
|
68
|
+
if field.is_a?(String)
|
|
69
|
+
@display_fields << field.split('.').last.to_sym
|
|
70
|
+
@select_fields << field
|
|
71
|
+
else
|
|
72
|
+
@display_fields << field
|
|
73
|
+
@select_fields << "`#{@klass.table_name}`.#{field}" if @klass.columns_hash.keys.include?(field.to_s)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
#################################################
|
|
79
|
+
|
|
80
|
+
def select_field
|
|
81
|
+
@select_field_array ||= begin
|
|
82
|
+
selects = @select_fields.to_a
|
|
83
|
+
@eaten_levels.each { |l| selects.concat l.select_field }
|
|
84
|
+
selects
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def include_field
|
|
89
|
+
@include_field_array ||= begin
|
|
90
|
+
includes = @includes.map { |i| i[:association].name }
|
|
91
|
+
@eaten_levels.each { |l| includes.concat l.include_field }
|
|
92
|
+
includes
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def receive(item)
|
|
97
|
+
return nil if item.nil? # method may be nil
|
|
98
|
+
attributes = {}
|
|
99
|
+
@eaten_levels.each do |level|
|
|
100
|
+
attributes.merge! level.receive(item)
|
|
101
|
+
end
|
|
102
|
+
@display_fields.each do |field|
|
|
103
|
+
attributes[field] = item.send(field)
|
|
104
|
+
end
|
|
105
|
+
@includes.each do |include|
|
|
106
|
+
value = item.send(include[:association].name)
|
|
107
|
+
value = value.is_a?(Enumerable) ? value.map { |e| include[:request_level].receive(e) } : include[:request_level].receive(value)
|
|
108
|
+
attributes[include[:name]] = value
|
|
109
|
+
end
|
|
110
|
+
@notations.each do |name, block|
|
|
111
|
+
attributes[name] = item.instance_eval(&block)
|
|
112
|
+
end
|
|
113
|
+
attributes
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
end
|
data/lib/flexible_api.rb
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
module FlexibleApi
|
|
2
|
+
|
|
3
|
+
autoload :RequestLevel, File.dirname(__FILE__) + '/flexible_api/request_level'
|
|
4
|
+
autoload :NoSuchRequestLevelError, File.dirname(__FILE__) + '/flexible_api/no_such_request_level_error'
|
|
5
|
+
|
|
6
|
+
@@flexible_models = []
|
|
7
|
+
|
|
8
|
+
def self.included(base)
|
|
9
|
+
base.send(:extend, ClassMethods)
|
|
10
|
+
base.send(:include, InstanceMethods)
|
|
11
|
+
@@flexible_models << base
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.flexible_models
|
|
15
|
+
@@flexible_models
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
module ClassMethods
|
|
19
|
+
|
|
20
|
+
def request_levels
|
|
21
|
+
@levels.nil? ? [] : @levels.keys
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Define a request level for this class
|
|
25
|
+
# Takes a name, and a block which defined the request level
|
|
26
|
+
def define_request_level(name, &block)
|
|
27
|
+
level = RequestLevel.new(name, self)
|
|
28
|
+
level.instance_eval &block
|
|
29
|
+
@levels ||= {}
|
|
30
|
+
@levels[name] = level
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Find a single element and load it at the given request level
|
|
34
|
+
def find_hash(id, options = {})
|
|
35
|
+
options.assert_valid_keys(:request_level)
|
|
36
|
+
level = find_level(options[:request_level])
|
|
37
|
+
record = self.find(id, :select => level.select_field.join(', '), :include => level.include_field)
|
|
38
|
+
level.receive record
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Find all of an element (or association) and load it at the given request level
|
|
42
|
+
def find_all_hash(options = {})
|
|
43
|
+
options.assert_valid_keys(:request_level)
|
|
44
|
+
level = find_level(options[:request_level])
|
|
45
|
+
records = self.all(:select => level.select_field.join(', '), :include => level.include_field)
|
|
46
|
+
records.map { |r| level.receive(r) }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def default_request_level(level_name)
|
|
50
|
+
@default_request_level_name = level_name
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Find a given level by name and return the request level
|
|
54
|
+
def find_level(name = nil)
|
|
55
|
+
@levels ||= {}
|
|
56
|
+
level = name.nil? ? load_default_request_level : @levels[name.to_sym]
|
|
57
|
+
raise NoSuchRequestLevelError.new(name, self.name) if level.nil?
|
|
58
|
+
level
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def load_default_request_level
|
|
64
|
+
@default_request_level ||=
|
|
65
|
+
if @default_request_level_name.nil?
|
|
66
|
+
level = RequestLevel.new(:default, self)
|
|
67
|
+
level.all_fields
|
|
68
|
+
level
|
|
69
|
+
else
|
|
70
|
+
self.find_level(@default_request_level_name)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
module InstanceMethods
|
|
77
|
+
|
|
78
|
+
# Return a hash of this element at the given request level (by name)
|
|
79
|
+
def to_hash(level_name)
|
|
80
|
+
level = self.class.find_level level_name
|
|
81
|
+
level.receive self
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
require 'bundler/setup'
|
|
2
|
+
require 'active_record'
|
|
3
|
+
|
|
4
|
+
ActiveRecord::Base.establish_connection(:database => 'spec/test.db', :adapter => 'sqlite3')
|
|
5
|
+
|
|
6
|
+
# require 'logger'
|
|
7
|
+
# ActiveRecord::Base.logger = Logger.new(STDOUT)
|
|
8
|
+
|
|
9
|
+
require File.dirname(__FILE__) + '/../lib/flexible_api'
|
metadata
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: flexible_api
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
hash: 29
|
|
5
|
+
prerelease:
|
|
6
|
+
segments:
|
|
7
|
+
- 0
|
|
8
|
+
- 0
|
|
9
|
+
- 1
|
|
10
|
+
version: 0.0.1
|
|
11
|
+
platform: ruby
|
|
12
|
+
authors:
|
|
13
|
+
- John Crepezzi
|
|
14
|
+
autorequire:
|
|
15
|
+
bindir: bin
|
|
16
|
+
cert_chain: []
|
|
17
|
+
|
|
18
|
+
date: 2011-03-28 00:00:00 +01:00
|
|
19
|
+
default_executable:
|
|
20
|
+
dependencies:
|
|
21
|
+
- !ruby/object:Gem::Dependency
|
|
22
|
+
name: rspec
|
|
23
|
+
prerelease: false
|
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
|
25
|
+
none: false
|
|
26
|
+
requirements:
|
|
27
|
+
- - ">="
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
hash: 3
|
|
30
|
+
segments:
|
|
31
|
+
- 0
|
|
32
|
+
version: "0"
|
|
33
|
+
type: :development
|
|
34
|
+
version_requirements: *id001
|
|
35
|
+
- !ruby/object:Gem::Dependency
|
|
36
|
+
name: sqlite3
|
|
37
|
+
prerelease: false
|
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
|
39
|
+
none: false
|
|
40
|
+
requirements:
|
|
41
|
+
- - ">="
|
|
42
|
+
- !ruby/object:Gem::Version
|
|
43
|
+
hash: 3
|
|
44
|
+
segments:
|
|
45
|
+
- 0
|
|
46
|
+
version: "0"
|
|
47
|
+
type: :development
|
|
48
|
+
version_requirements: *id002
|
|
49
|
+
- !ruby/object:Gem::Dependency
|
|
50
|
+
name: activerecord
|
|
51
|
+
prerelease: false
|
|
52
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
|
53
|
+
none: false
|
|
54
|
+
requirements:
|
|
55
|
+
- - ">="
|
|
56
|
+
- !ruby/object:Gem::Version
|
|
57
|
+
hash: 3
|
|
58
|
+
segments:
|
|
59
|
+
- 0
|
|
60
|
+
version: "0"
|
|
61
|
+
type: :runtime
|
|
62
|
+
version_requirements: *id003
|
|
63
|
+
description: API for making APIs
|
|
64
|
+
email: john.crepezzi@gmail.com
|
|
65
|
+
executables: []
|
|
66
|
+
|
|
67
|
+
extensions: []
|
|
68
|
+
|
|
69
|
+
extra_rdoc_files: []
|
|
70
|
+
|
|
71
|
+
files:
|
|
72
|
+
- lib/flexible_api/no_such_request_level_error.rb
|
|
73
|
+
- lib/flexible_api/request_level.rb
|
|
74
|
+
- lib/flexible_api/version.rb
|
|
75
|
+
- lib/flexible_api.rb
|
|
76
|
+
- spec/spec_helper.rb
|
|
77
|
+
has_rdoc: true
|
|
78
|
+
homepage: http://github.com/seejohnrun/flexible_api
|
|
79
|
+
licenses: []
|
|
80
|
+
|
|
81
|
+
post_install_message:
|
|
82
|
+
rdoc_options: []
|
|
83
|
+
|
|
84
|
+
require_paths:
|
|
85
|
+
- lib
|
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
87
|
+
none: false
|
|
88
|
+
requirements:
|
|
89
|
+
- - ">="
|
|
90
|
+
- !ruby/object:Gem::Version
|
|
91
|
+
hash: 3
|
|
92
|
+
segments:
|
|
93
|
+
- 0
|
|
94
|
+
version: "0"
|
|
95
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
96
|
+
none: false
|
|
97
|
+
requirements:
|
|
98
|
+
- - ">="
|
|
99
|
+
- !ruby/object:Gem::Version
|
|
100
|
+
hash: 3
|
|
101
|
+
segments:
|
|
102
|
+
- 0
|
|
103
|
+
version: "0"
|
|
104
|
+
requirements: []
|
|
105
|
+
|
|
106
|
+
rubyforge_project: flexible_api
|
|
107
|
+
rubygems_version: 1.6.2
|
|
108
|
+
signing_key:
|
|
109
|
+
specification_version: 3
|
|
110
|
+
summary: A flexible API for making APIs
|
|
111
|
+
test_files:
|
|
112
|
+
- spec/spec_helper.rb
|