serviceable 0.2 → 0.4

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.
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.