flexible_api 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ module FlexibleApi
2
+
3
+ class NoSuchRequestLevelError < StandardError
4
+
5
+ def initialize(rl_name, klass_name)
6
+ @message = "There is no request level '#{rl_name}' for #{klass_name}"
7
+ end
8
+
9
+ def message
10
+ @message
11
+ end
12
+
13
+ end
14
+
15
+ end
@@ -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
@@ -0,0 +1,5 @@
1
+ module FlexibleApi
2
+
3
+ VERSION = '0.0.1'
4
+
5
+ end
@@ -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
@@ -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