Module: RestfulJson::Controller::ActsAsRestfulJson

Extended by:
ActiveSupport::Concern
Defined in:
lib/restful_json/controller.rb

Defined Under Namespace

Modules: ClassMethods

Constant Summary collapse

NILS =
['NULL','null','nil']

Instance Method Summary collapse

Instance Method Details

#convert_request_param_value_for_filtering(attr_sym, value) ⇒ Object



166
167
168
# File 'lib/restful_json/controller.rb', line 166

def convert_request_param_value_for_filtering(attr_sym, value)
  value && NILS.include?(value) ? nil : value
end

#createObject

The controller’s create (post) method to create a resource.



296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
# File 'lib/restful_json/controller.rb', line 296

def create
  authorize! :create, @model_class
  @value = @model_class.new(permitted_params)
  @value.save
  instance_variable_set(@model_at_singular_name_sym, @value)
  if RestfulJson.return_resource
    respond_with(@value) do |format|
      format.json do
        if @value.errors.empty?
          render json: @value, status: :created
        else
          render json: {errors: @value.errors}, status: :unprocessable_entity
        end
      end
    end
  else
    respond_with @value
  end
end

#destroyObject

The controller’s destroy (delete) method to destroy a resource.



339
340
341
342
343
344
345
# File 'lib/restful_json/controller.rb', line 339

def destroy
  # to_s as safety measure for vulnerabilities similar to CVE-2013-1854
  @value = @model_class.find(params[:id].to_s)
  @value.destroy
  instance_variable_set(@model_at_singular_name_sym, @value)
  respond_with @value
end

#editObject

The controller’s edit method (e.g. used for edit record in html format).



289
290
291
292
293
# File 'lib/restful_json/controller.rb', line 289

def edit
  # to_s as safety measure for vulnerabilities similar to CVE-2013-1854
  @value = @model_class.find(params[:id].to_s)
  instance_variable_set(@model_at_singular_name_sym, @value)
end

#indexObject

The controller’s index (list) method to list resources.

Note: this method be alias_method’d by query_for, so it is more than just index.



173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
# File 'lib/restful_json/controller.rb', line 173

def index
  t = @model_class.arel_table
  value = @model_class.scoped # returns ActiveRecord::Relation equivalent to select with no where clause
  custom_query = self.action_to_query[params[:action].to_s]
  if custom_query
    value = custom_query.call(t, value)
  end

  self.param_to_query.each do |param_name, param_query|
    if params[param_name]
      # to_s as safety measure for vulnerabilities similar to CVE-2013-1854
      value = param_query.call(t, value, params[param_name].to_s)
    end
  end

  self.param_to_through.each do |param_name, through_array|
    if params[param_name]
      # build query
      # e.g. SomeModel.scoped.joins({:assoc_name => {:sub_assoc => {:sub_sub_assoc => :sub_sub_sub_assoc}}).where(sub_sub_sub_assoc_model_table_name: {column_name: value})
      last_model_class = @model_class
      joins = nil # {:assoc_name => {:sub_assoc => {:sub_sub_assoc => :sub_sub_sub_assoc}}
      through_array.each do |association_or_attribute|
        if association_or_attribute == through_array.last
          # must convert param value to string before possibly using with ARel because of CVE-2013-1854, fixed in: 3.2.13 and 3.1.12 
          # https://groups.google.com/forum/?fromgroups=#!msg/rubyonrails-security/jgJ4cjjS8FE/BGbHRxnDRTIJ
          value = value.joins(joins).where(last_model_class.table_name.to_sym => {association_or_attribute => params[param_name].to_s})
        else
          found_classes = last_model_class.reflections.collect {|association_name, reflection| reflection.class_name.constantize if association_name.to_sym == association_or_attribute}.compact
          if found_classes.size > 0
            last_model_class = found_classes[0]
          else
            # bad can_filter_by :through found at runtime
            raise "Association #{association_or_attribute.inspect} not found on #{last_model_class}."
          end

          if joins.nil?
            joins = association_or_attribute
          else
            joins = {association_or_attribute => joins}
          end
        end
      end
    end
  end

  self.param_to_attr_and_arel_predicate.keys.each do |param_name|
    options = param_to_attr_and_arel_predicate[param_name][2]
    param = params[param_name] || options[:with_default]
    if param.present? && param_to_attr_and_arel_predicate[param_name]
      attr_sym = param_to_attr_and_arel_predicate[param_name][0]
      predicate_sym = param_to_attr_and_arel_predicate[param_name][1]
      if predicate_sym == :eq
        value = value.where(attr_sym => convert_request_param_value_for_filtering(attr_sym, param))
      else
        one_or_more_param = param.split(self.filter_split).collect{|v|convert_request_param_value_for_filtering(attr_sym, v)}
        value = value.where(t[attr_sym].try(predicate_sym, one_or_more_param))
      end
    end
  end

  if params[:page] && self.supported_functions.include?(:page)
    page = params[:page].to_i
    page = 1 if page < 1 # to avoid people using this as a way to get all records unpaged, as that probably isn't the intent?
    #TODO: to_s is hack to avoid it becoming an Arel::SelectManager for some reason which not sure what to do with
    value = value.skip((self.number_of_records_in_a_page * (page - 1)).to_s)
    value = value.take((self.number_of_records_in_a_page).to_s)
  end

  if params[:skip] && self.supported_functions.include?(:skip)
    # to_s as safety measure for vulnerabilities similar to CVE-2013-1854
    value = value.skip(params[:skip].to_s)
  end

  if params[:take] && self.supported_functions.include?(:take)
    # to_s as safety measure for vulnerabilities similar to CVE-2013-1854
    value = value.take(params[:take].to_s)
  end

  if params[:uniq] && self.supported_functions.include?(:uniq)
    value = value.uniq
  end

  # these must happen at the end and are independent
  if params[:count] && self.supported_functions.include?(:count)
    value = value.count.to_i
  elsif params[:page_count] && self.supported_functions.include?(:page_count)
    count_value = value.count.to_i # this executes the query so nothing else can be done in AREL
    value = (count_value / self.number_of_records_in_a_page) + (count_value % self.number_of_records_in_a_page ? 1 : 0)
  else
    self.ordered_by.each do |attr_to_direction|
      # this looks nasty, but makes no sense to iterate keys if only single of each
      value = value.order(t[attr_to_direction.keys[0]].call(attr_to_direction.values[0]))
    end
    value = value.to_a
  end

  @value = value
  instance_variable_set(@model_at_plural_name_sym, @value)
  respond_with @value
end

#initializeObject

In initialize we:

  • guess model name, if unspecified, from controller name

  • define instance variables containing model name

  • define the (model_plural_name)_url method, needed if controllers are not in the same module as the models

Note: if controller name is not based on model name and controller is in different module than model, you’ll need to redefine the appropriate method(s) to return urls if needed.



145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/restful_json/controller.rb', line 145

def initialize
  super

  # if not set, use controller classname
  qualified_controller_name = self.class.name.chomp('Controller')
  @model_class = self.model_class || qualified_controller_name.split('::').last.singularize.constantize

  raise "#{self.class.name} failed to initialize. self.model_class was nil in #{self} which shouldn't happen!" if @model_class.nil?
  raise "#{self.class.name} assumes that #{self.model_class} extends ActiveRecord::Base, but it didn't. Please fix, or remove this constraint." unless @model_class.ancestors.include?(ActiveRecord::Base)

  @model_singular_name = self.model_singular_name || self.model_class.name.underscore
  @model_plural_name = self.model_plural_name || @model_singular_name.pluralize
  @model_at_plural_name_sym = "@#{@model_plural_name}".to_sym
  @model_at_singular_name_sym = "@#{@model_singular_name}".to_sym
  underscored_modules_and_underscored_plural_model_name = qualified_controller_name.gsub('::','_').underscore

  # This is a workaround for controllers that are in a different module than the model only works if the controller's base part of the unqualified name in the plural model name.
  # If the model name is different than the controller name, you will need to define methods to return the right urls.
  class_eval "def #{@model_plural_name}_url;#{underscored_modules_and_underscored_plural_model_name}_url;end;def #{@model_singular_name}_url(record);#{underscored_modules_and_underscored_plural_model_name.singularize}_url(record);end"        
end

#newObject

The controller’s new method (e.g. used for new record in html format).



283
284
285
286
# File 'lib/restful_json/controller.rb', line 283

def new
  @value = @model_class.new
  respond_with @value
end

#showObject

The controller’s show (get) method to return a resource.



275
276
277
278
279
280
# File 'lib/restful_json/controller.rb', line 275

def show
  # to_s as safety measure for vulnerabilities similar to CVE-2013-1854
  @value = @model_class.find(params[:id].to_s)
  instance_variable_set(@model_at_singular_name_sym, @value)
  respond_with @value
end

#updateObject

The controller’s update (put) method to update a resource.



317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
# File 'lib/restful_json/controller.rb', line 317

def update
  authorize! :update, @model_class
  # to_s as safety measure for vulnerabilities similar to CVE-2013-1854
  @value = @model_class.find(params[:id].to_s)
  @value.update_attributes(permitted_params)
  instance_variable_set(@model_at_singular_name_sym, @value)
  if RestfulJson.return_resource
    respond_with(@value) do |format|
      format.json do
        if @value.errors.empty?
          render json: @value, status: :ok
        else
          render json: {errors: @value.errors}, status: :unprocessable_entity
        end
      end
    end
  else
    respond_with @value
  end
end