diff --git a/.gitignore b/.gitignore index 6a01cab..47b61c0 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ .idea *~ coverage/ +html/ diff --git a/Rakefile b/Rakefile index fa9a284..24088b3 100644 --- a/Rakefile +++ b/Rakefile @@ -1,5 +1,12 @@ require 'rubygems' + +require 'rdoc/task' +RDoc::Task.new do |rdoc| + rdoc.main = "README.md" + rdoc.rdoc_files.include("README.md", "lib/**/*.rb") +end + require 'rspec/core/rake_task' RSpec::Core::RakeTask.new do |task| task.rspec_opts = ["-c", "-f progress", "-r ./spec/spec_helper.rb"] diff --git a/lib/yodatra/models_controller.rb b/lib/yodatra/models_controller.rb index e355042..f59204f 100644 --- a/lib/yodatra/models_controller.rb +++ b/lib/yodatra/models_controller.rb @@ -5,48 +5,49 @@ module Yodatra # Simply create your controller that inherits from this class, keeping the naming convention. # # For example, given a User model, creating a class UsersController < Yodatra::ModelsController, it will expose these routes: - # GET /users - # => retrieves all users (attributes exposed are limited by the read_scope method defined in the UsersController) # - # GET /users/:id - # => retrieves a user (attributes exposed are limited by the read_scope method defined in the UsersController) + # GET /users + # > retrieves all users (attributes exposed are limited by the read_scope method defined in the UsersController) # - # POST /users - # => creates a user (attributes assignable are limited by the user_params method defined in the UsersController) + # GET /users/:id + # > retrieves a user (attributes exposed are limited by the read_scope method defined in the UsersController) # - # PUT /users/:id - # => updates a user (attributes assignable are limited by the user_params method defined in the UsersController) + # POST /users + # > creates a user (attributes assignable are limited by the user_params method defined in the UsersController) # - # DELETE /users/:id - # => deletes a user + # PUT /users/:id + # > updates a user (attributes assignable are limited by the user_params method defined in the UsersController) + # + # DELETE /users/:id + # > deletes a user # # If your model is referenced by another model, nested routes are also created for you. And you don't need to worry about the references/joins, they are done automaticly! # For example, imagine a Team model that has many Users, the following routes will be exposed: - # GET /team/:team_id/users, GET /team/:team_id/users/:id, POST /team/:team_id/users, PUT /team/:team_id/users/:id and DESTROY /team/:team_id/users/:id # - # _Note_: You can disable any of these five actions by using the `#disable` class method - # and giving in parameters the list of actions you want to disable - # e.g. `disable :read, :read_all, :create, :update, :delete` + # GET /team/:team_id/users + # + # GET /team/:team_id/users/:id + # + # POST /team/:team_id/users + # + # PUT /team/:team_id/users/:id + # + # DESTROY /team/:team_id/users/:id # - # _Note2_: You can enable a special "search" action by using the `#enable_search_on` class method + # === Note: + # You can disable any of these actions by using the ::disable class method + # and providing the list of actions you want to disable + # disable :read, :read_all, :create, :update, :delete, :nested_read_all, :nested_delete + # + # === Extra: + # You can enable a special "search" action by using the #enable_search_on class method + # enable_search_on :name class ModelsController < Sinatra::Base before do content_type 'application/json' end - # Generic route to target ONE resource - ONE_ROUTE = - %r{\A/([\w]+?)/([0-9]+)(?:/([\w]+?)/([0-9]+)){0,1}\Z} - - # Generic route to target ALL resources - ALL_ROUTE = - %r{\A/([\w]+?)(?:/([0-9]+)/([\w]+?)){0,1}\Z} - - # Search route - SEARCH_ROUTE = - %r{\A/([\w]+?)(?:/([0-9]+)/([\w]+?)){0,1}/search\Z} - READ_ALL = :read_all get ALL_ROUTE do retrieve_resources READ_ALL do |resource| @@ -101,29 +102,9 @@ class ModelsController < Sinatra::Base end end - # Defines a nested route or not and retrieves the correct resource (or resources) - # @param disables is the name to check if it was disabled - # @param &block to be yield with the retrieved resource - def retrieve_resources(disables) - pass unless involved? - no_route if disabled? disables - - model = model_name.constantize - nested = nested_resources if nested? - - if model.nil? || nested.nil? && nested? - raise ActiveRecord::RecordNotFound - else - model = nested if nested? - one_id = nested? ? params[:captures].fourth : params[:captures].second if params[:captures].length == 4 - model = model.find one_id unless one_id.nil? - yield(model) - end - rescue ActiveRecord::RecordNotFound - record_not_found - end - class << self + private + def model_name self.name.split('::').last.gsub(/sController/, '') end @@ -132,6 +113,8 @@ def model model_name.constantize end + public + # This helper gives the ability to disable default root by specifying # a list of routes to disable. # @param *opts list of routes to disable (e.g. :create, :destroy) @@ -143,6 +126,11 @@ def disable(*opts) end end + # This class method enables the search routes `/resoures/search?q=search+terms` for the model. + # The search will be performed on all the attributes given in parameter of this method. + # E.g. if you enabled the search on `:name` and `:email` attrs + # a GET /resources/search?q=john+doe + # will return all `Resource` instance where the name or the email matches either "john" or "doe" def enable_search_on(*attributes) self.instance_eval do get SEARCH_ROUTE do @@ -170,6 +158,40 @@ def enable_search_on(*attributes) private + # Generic route to target ONE resource + ONE_ROUTE = + %r{\A/([\w]+?)/([0-9]+)(?:/([\w]+?)/([0-9]+)){0,1}\Z} + + # Generic route to target ALL resources + ALL_ROUTE = + %r{\A/([\w]+?)(?:/([0-9]+)/([\w]+?)){0,1}\Z} + + # Search route + SEARCH_ROUTE = + %r{\A/([\w]+?)(?:/([0-9]+)/([\w]+?)){0,1}/search\Z} + + # Defines a nested route or not and retrieves the correct resource (or resources) + # @param disables is the name to check if it was disabled + # @param &block to be yield with the retrieved resource + def retrieve_resources(disables) + pass unless involved? + no_route if disabled? disables + + model = model_name.constantize + nested = nested_resources if nested? + + if model.nil? || nested.nil? && nested? + raise ActiveRecord::RecordNotFound + else + model = nested if nested? + one_id = nested? ? params[:captures].fourth : params[:captures].second if params[:captures].length == 4 + model = model.find one_id unless one_id.nil? + yield(model) + end + rescue ActiveRecord::RecordNotFound + record_not_found + end + def nested? params[:captures].length >= 3 && params[:captures].first(3).none?(&:nil?) end