serviceable 0.2 → 0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (2) hide show
  1. data/lib/serviceable.rb +100 -22
  2. metadata +4 -4
@@ -15,50 +15,58 @@ module Serviceable
15
15
  #
16
16
  def acts_as_service(object,options={})
17
17
 
18
- before_filter :assign_new_instance, :only => :create
19
- before_filter :assign_existing_instance, :only => [ :show, :update, :destroy ]
20
- before_filter :assign_instances, :only => :index
18
+ before_filter :assign_new_instance, only: :create
19
+ before_filter :assign_existing_instance, only: [ :show, :update, :destroy ]
20
+ before_filter :assign_collection, only: [ :index, :count ]
21
+ before_filter :did_assign_collection, only: [ :index, :count ]
21
22
 
22
23
  define_method("index") do
23
24
  respond_to do |format|
24
- format.json { eval "render :json => @#{object.to_s.pluralize}.to_json(merge_options(options[:index]))" }
25
- format.xml { eval "render :xml => @#{object.to_s.pluralize}.to_xml(merge_options(options[:index]))" }
25
+ format.json { render json: @collection.to_json(merge_options(options[:index])) }
26
+ format.xml { render xml: @collection.to_xml(merge_options(options[:index])) }
27
+ end
28
+ end
29
+
30
+ define_method("count") do
31
+ respond_to do |format|
32
+ format.json { render json: @collection.count }
33
+ format.xml { render xml: @collection.count }
26
34
  end
27
35
  end
28
36
 
29
37
  define_method("create") do
30
38
  respond_to do |format|
31
- if eval "@#{object}.save"
32
- format.json { eval "render :json => @#{object}" }
33
- format.xml { eval "render :xml => @#{object}" }
39
+ if @instance.save
40
+ format.json { render json: @instance }
41
+ format.xml { render xml: @instance }
34
42
  else
35
- format.json { eval "render :json => { :errors => @#{object}.errors.full_messages }, :status => :unprocessable_entity" }
36
- format.xml { eval "render :xml => { :errors => @#{object}.errors.full_messages }, :status => :unprocessable_entity" }
43
+ format.json { render json: { errors: @instance.errors.full_messages }, status: :unprocessable_entity }
44
+ format.xml { render xml: { errors: @instance.errors.full_messages }, status: :unprocessable_entity }
37
45
  end
38
46
  end
39
47
  end
40
48
 
41
49
  define_method("show") do
42
50
  respond_to do |format|
43
- format.json { eval "render :json => @#{object}.to_json(options[:show])" }
44
- format.xml { eval "render :xml => @#{object}.to_xml(options[:show])" }
51
+ format.json { render json: @instance.to_json(merge_options(options[:show])) }
52
+ format.xml { render xml: @instance.to_xml(merge_options(options[:show])) }
45
53
  end
46
54
  end
47
55
 
48
56
  define_method("update") do
49
57
  respond_to do |format|
50
- if eval "@#{object}.update_attributes(params[object])"
58
+ if @instance.update_attributes(params[object])
51
59
  format.json { head :ok }
52
60
  format.xml { head :ok }
53
61
  else
54
- format.json { eval "render :json => { :errors => @#{object}.errors.full_messages }, :status => :unprocessable_entity" }
55
- format.xml { eval "render :xml => { :errors => @#{object}.errors.full_messages }, :status => :unprocessable_entity" }
62
+ format.json { render json: { errors: @instance.errors.full_messages }, status: :unprocessable_entity }
63
+ format.xml { render xml: { errors: @instance.errors.full_messages }, status: :unprocessable_entity }
56
64
  end
57
65
  end
58
66
  end
59
67
 
60
68
  define_method("destroy") do
61
- eval "@#{object}.destroy"
69
+ @instance.destroy
62
70
 
63
71
  respond_to do |format|
64
72
  format.json { head :no_content }
@@ -66,27 +74,97 @@ module Serviceable
66
74
  end
67
75
  end
68
76
 
77
+ # query string params can be given in the following formats:
78
+ # only=field1,field2
79
+ # except=field1,field2
80
+ # include=assoc1
81
+ #
82
+ # if an included association is present, only and except params can be nested
83
+ # include[user][except]=encrypted_password
84
+ # include[user][only][]=first_name&include[user][only][]=last_name
85
+ # include[user][only]=first_name,last_name
69
86
  define_method("merge_options") do |options={}|
70
87
  merged_options = options || {}
71
88
  for key in [:only, :except, :include]
72
- merged_options = merged_options.merge({key => params[key].split(",")}) if params[key]
89
+ opts = {key => params[key]} if params[key]
90
+ merged_options = merged_options.merge(opts) if opts
73
91
  end
92
+ merged_options = deep_split(merged_options)
74
93
  return merged_options
75
94
  end
76
95
 
77
96
  define_method("assign_existing_instance") do
78
- eval "@#{object} = object.to_s.camelize.constantize.find(params[:id])"
97
+ @instance = object.to_s.camelize.constantize
98
+ if params[:include].kind_of?(Hash)
99
+ @instance = @instance.includes(params[:include].keys)
100
+ end
101
+ if params[:include].kind_of?(String)
102
+ @instance = @instance.includes(params[:include].split(",").map(&:to_sym))
103
+ end
104
+ @instance = @instance.find(params[:id])
79
105
  end
80
106
 
81
107
  define_method("assign_new_instance") do
82
- eval "@#{object} = object.to_s.camelize.constantize.new(params[:#{object}])"
108
+ @instance = object.to_s.camelize.constantize.new(params[object])
109
+ end
110
+
111
+ # query string params can be used to filter collections
112
+ #
113
+ # filters apply on associated collections using the following conventions:
114
+ # where[user][category]=Expert
115
+ # where[user][created_at][gt]=20130807T12:34:56.789Z
116
+ #
117
+ # filters can be constructed with AND and OR behavior
118
+ # where[tags][id][in]=123,234,345 (OR)
119
+ # where[tags][id]=123&where[tags][id]=234 (AND)
120
+ define_method("assign_collection") do
121
+ @collection = object.to_s.camelize.constantize
122
+ if params[:include].kind_of?(Hash)
123
+ for assoc in params[:include].keys
124
+ @collection = @collection.includes(assoc.to_sym)
125
+ end
126
+ end
127
+ if params[:include].kind_of?(String)
128
+ @collection = @collection.includes(params[:include].split(",").map(&:to_sym))
129
+ end
130
+ for assoc in (params[:where].keys rescue [])
131
+ attrs = params[:where][assoc]
132
+ if attrs.kind_of?(Hash)
133
+ for target_column in attrs.keys
134
+ op = :eq if attrs[target_column].kind_of?(String)
135
+ op ||= attrs[target_column].keys[0].to_sym
136
+ value = attrs[target_column] if op==:eq
137
+ value ||= is_time_column?(target_column) ? Time.parse(attrs[target_column][op]) : attrs[target_column][op]
138
+ unless assoc.to_sym==object.to_s.pluralize.to_sym
139
+ @collection = @collection.includes(assoc)
140
+ end
141
+ puts "op: #{op}"
142
+ if op==:gt
143
+ @collection = @collection.where("#{assoc}.#{target_column} > ?",value)
144
+ elsif op==:lt
145
+ @collection = @collection.where("#{assoc}.#{target_column} < ?",value)
146
+ elsif op==:in
147
+ @collection = @collection.where("#{assoc}.#{target_column} IN (?)",value)
148
+ elsif op==:eq
149
+ @collection = @collection.where(assoc => { target_column => value })
150
+ end
151
+ end
152
+ else
153
+ @collection = @collection.includes(assoc).where(assoc => attrs)
154
+ end
155
+ end
83
156
  end
84
157
 
85
- define_method("assign_instances") do
86
- eval "@#{object.to_s.pluralize} = object.to_s.camelize.constantize.scoped"
158
+ # designed to traverse an entire hash, replacing delimited strings with arrays of symbols
159
+ define_method("deep_split") do |hash={},pivot=','|
160
+ Hash[hash.map {|k,v| [k.to_sym,v.kind_of?(String) ? v.split(pivot).map(&:to_sym) : (v.kind_of?(Hash) ? deep_split(v,pivot) : v)]}]
87
161
  end
88
- end
89
162
 
163
+ define_method("is_time_column?") do |column|
164
+ !!column[-3,3]=='_at'
165
+ end
166
+ end
167
+
90
168
  end
91
169
 
92
170
  end
metadata CHANGED
@@ -1,12 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: serviceable
3
3
  version: !ruby/object:Gem::Version
4
- hash: 15
4
+ hash: 3
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 2
9
- version: "0.2"
8
+ - 4
9
+ version: "0.4"
10
10
  platform: ruby
11
11
  authors:
12
12
  - Aubrey Goodman
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2013-07-27 00:00:00 Z
17
+ date: 2013-08-08 00:00:00 Z
18
18
  dependencies: []
19
19
 
20
20
  description: Decorate your controller classes with acts_as_service :model_name, and instantly support JSON/XML CRUD interface. Override any callback method to customize your endpoint. Allow client to specify response contents using query string filter parameters.