diff --git a/README.md b/README.md index df1ef0669..cb564a0e7 100644 --- a/README.md +++ b/README.md @@ -345,7 +345,7 @@ JSON::API::ResourceSerializer.new.serialize(post, include: ['comments','author', people: [:id, :email, :comments], posts: [:id, :title, :author], tags: [:name], - comments: [:id, :body, :post]})) + comments: [:id, :body, :post]}) ``` ##### Other fields @@ -363,3 +363,7 @@ called. This can be used to filter the fields based on scope or other criteria. 3. Commit your changes (`git commit -am 'Add some feature'`) 4. Push to the branch (`git push origin my-new-feature`) 5. Create new Pull Request + +## License + +JSON::API::Resources is released under the [MIT License](http://www.opensource.org/licenses/MIT). \ No newline at end of file diff --git a/json-api-resources.gemspec b/json-api-resources.gemspec index ab2601666..598306a38 100644 --- a/json-api-resources.gemspec +++ b/json-api-resources.gemspec @@ -17,12 +17,9 @@ Gem::Specification.new do |spec| spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) spec.require_paths = ['lib'] + spec.required_ruby_version = '>= 1.9.3' spec.add_development_dependency 'bundler', '~> 1.5' spec.add_development_dependency 'rake' spec.add_development_dependency 'minitest' - - spec.add_dependency 'activemodel' - spec.add_dependency 'activesupport' - spec.add_dependency 'rails' end diff --git a/lib/json/api/resource.rb b/lib/json/api/resource.rb index 25e5022cd..90bf474c0 100644 --- a/lib/json/api/resource.rb +++ b/lib/json/api/resource.rb @@ -45,7 +45,7 @@ def attributes(*attrs) @_attributes.merge attrs attrs.each do |attr| define_method attr do - @object.read_attribute_for_serialization attr + @object.method(attr).call end unless method_defined?(attr) end end @@ -53,7 +53,7 @@ def attributes(*attrs) def attribute(attr) @_attributes.add attr define_method attr do - @object.read_attribute_for_serialization attr + @object.method(attr).call end unless method_defined?(attr) end @@ -186,7 +186,7 @@ def _associate(klass, *attrs) key = @_associations[attr].key define_method key do - @object.read_attribute_for_serialization key + @object.method(key).call end unless method_defined?(key) define_method "_#{attr}_object" do @@ -201,7 +201,7 @@ def _associate(klass, *attrs) key = @_associations[attr].key define_method key do - @object.read_attribute_for_serialization key + @object.method(key).call end unless method_defined?(key) define_method "#{key}=" do |values| diff --git a/lib/json/api/resource_controller.rb b/lib/json/api/resource_controller.rb index ecf26f237..143922a48 100644 --- a/lib/json/api/resource_controller.rb +++ b/lib/json/api/resource_controller.rb @@ -11,77 +11,64 @@ module API class ResourceController < ActionController::Base include ResourceFor - def index - fields = parse_fields(params) - include = parse_includes(params[:include]) - filters = parse_filters(params) + before_filter :parse_fields, except: [:destroy] + before_filter :parse_includes, except: [:destroy] + before_filter :parse_filters, only: [:index] + def index render json: JSON::API::ResourceSerializer.new.serialize( - resource_klass.find({filters: filters}), - include: include, - fields: fields + resource_klass.find({filters: @filters}), + include: @includes, + fields: @fields ) rescue Exception => e handle_json_api_error(e) end def show - fields = parse_fields(params) - include = parse_includes(params[:include]) - - klass = resource_klass - - ids = parse_id_array(params[klass._key]) + ids = parse_id_array(params[resource_klass._key]) resources = [] - klass.transaction do - ids.each do |id| - resources.push(klass.find_by_key(id)) - end + ids.each do |id| + resources.push(resource_klass.find_by_key(id)) end render json: JSON::API::ResourceSerializer.new.serialize( resources, - include: include, - fields: fields + include: @includes, + fields: @fields ) rescue Exception => e handle_json_api_error(e) end def create - fields = parse_fields(params) - include = parse_includes(params[:include]) - - klass = resource_klass - checked_params = verify_params(params, klass, klass.createable(klass._updateable_associations | klass._attributes.to_a)) - update_and_respond_with(klass.new, checked_params[0], checked_params[1], include: include, fields: fields) + checked_params = verify_params(params, + resource_klass, + resource_klass.createable(resource_klass._updateable_associations | resource_klass._attributes.to_a)) + update_and_respond_with(resource_klass.new, checked_params[0], checked_params[1], include: @includes, fields: @fields) rescue Exception => e handle_json_api_error(e) end def update - fields = parse_fields(params) - include = parse_includes(params[:include]) - - klass = resource_klass - checked_params = verify_params(params, klass, klass.updateable(klass._updateable_associations | klass._attributes.to_a)) + checked_params = verify_params(params, + resource_klass, + resource_klass.updateable(resource_klass._updateable_associations | resource_klass._attributes.to_a)) - return unless obj = klass.find_by_key(params[klass._key]) + return unless obj = resource_klass.find_by_key(params[resource_klass._key]) - update_and_respond_with(obj, checked_params[0], checked_params[1], include: include, fields: fields) + update_and_respond_with(obj, checked_params[0], checked_params[1], include: @include, fields: @fields) rescue Exception => e handle_json_api_error(e) end def destroy - klass = resource_klass + ids = parse_id_array(params[resource_klass._key]) - ids = parse_id_array(params[klass._key]) - - klass.transaction do + resource_klass.transaction do ids.each do |id| - klass.find_by_key(id).destroy + resource_klass.find_by_key(id).destroy end end render status: :no_content, json: nil @@ -180,13 +167,16 @@ def verify_params(params, klass, resource_param_set) return checked_params, checked_associations end - def parse_includes(includes) + def parse_includes + includes = params[:include] included_resources = [] included_resources += CSV.parse_line(includes) unless includes.nil? || includes.empty? - included_resources + @includes = included_resources + rescue Exception => e + handle_json_api_error(e) end - def parse_filters(params) + def parse_filters # Coerce :ids -> :id if params[:ids] params[:id] = params[:ids] @@ -206,7 +196,9 @@ def parse_filters(params) raise JSON::API::Errors::FilterNotAllowed.new(filter) end end - filters + @filters = filters + rescue Exception => e + handle_json_api_error(e) end def is_filter_association?(filter) @@ -215,12 +207,13 @@ def is_filter_association?(filter) def parse_id_array(raw) ids = [] - return raw.split(/,/).collect do |id| + raw.split(/,/).collect do |id| ids.push verify_id(resource_klass, id) end + return ids end - def parse_fields(params) + def parse_fields fields = {} # Extract the fields for each type from the fields parameters @@ -259,7 +252,9 @@ def parse_fields(params) raise JSON::API::Errors::InvalidField.new(type, 'nil') end end - fields + @fields = fields + rescue Exception => e + handle_json_api_error(e) end def verify_filter(filter, raw) diff --git a/test/controllers/controller_test.rb b/test/controllers/controller_test.rb index 4017e6e64..da9b59a0b 100644 --- a/test/controllers/controller_test.rb +++ b/test/controllers/controller_test.rb @@ -431,3 +431,68 @@ def test_get_person_as_author_by_name_filter assert_equal 2, json_response['authors'][0]['links']['posts'].size end end + +class BreedsControllerTest < ActionController::TestCase + def test_poro_index + get :index + assert_response :success + assert_equal 0, json_response['breeds'][0]['id'] + assert_equal 'persian', json_response['breeds'][0]['name'] + end + + def test_poro_show + get :show, {id: '0'} + assert_response :success + assert_equal 1, json_response['breeds'].size + assert_equal 0, json_response['breeds'][0]['id'] + assert_equal 'persian', json_response['breeds'][0]['name'] + end + + def test_poro_show_multiple + get :show, {id: '0,2'} + assert_response :success + assert_equal 2, json_response['breeds'].size + assert_equal 0, json_response['breeds'][0]['id'] + assert_equal 'persian', json_response['breeds'][0]['name'] + assert_equal 2, json_response['breeds'][1]['id'] + assert_equal 'sphinx', json_response['breeds'][1]['name'] + end + + def test_poro_create_simple + post :create, { breeds: { + name: 'tabby' + } + } + + assert_response :created + assert_equal 1, json_response['breeds'].size + assert_equal 'tabby', json_response['breeds'][0]['name'] + end + + def test_poro_create_update + post :create, { breeds: { + name: 'calic' + } + } + + assert_response :created + assert_equal 1, json_response['breeds'].size + assert_equal 'calic', json_response['breeds'][0]['name'] + + post :update, {id: json_response['breeds'][0]['id'], breeds: { + name: 'calico' + } + } + assert_response :success + assert_equal 1, json_response['breeds'].size + assert_equal 'calico', json_response['breeds'][0]['name'] + end + + def test_poro_delete + initial_count = $breed_data.breeds.keys.count + post :destroy, {id: '3'} + assert_response :no_content + assert_equal initial_count - 1, $breed_data.breeds.keys.count + end + +end \ No newline at end of file diff --git a/test/fixtures/active_record.rb b/test/fixtures/active_record.rb index cc8f3b59a..c9e57075d 100644 --- a/test/fixtures/active_record.rb +++ b/test/fixtures/active_record.rb @@ -100,6 +100,61 @@ class ExpenseEntry < ActiveRecord::Base belongs_to :currency, class_name: 'Currency', foreign_key: 'currency_code' end +class Breed + + def initialize(id = nil, name = nil) + if id.nil? + @id = $breed_data.new_id + $breed_data.add(self) + else + @id = id + end + @name = name + end + + attr_accessor :id, :name + + def update!(attributes) + # For production code it would be better to have a method that loops over attributes and sets local variables as appropriate + @name = attributes[:name] if attributes[:name] + end + + def destroy + $breed_data.remove(@id) + end + +end + +class BreedData + def initialize + @breeds = {} + end + + def breeds + @breeds + end + + def new_id + @breeds.keys.max + 1 + end + + def add(breed) + @breeds[breed.id] = breed + end + + def remove(id) + @breeds.delete(id) + end + +end + +### PORO Data - don't do this in a production app +$breed_data = BreedData.new +$breed_data.add(Breed.new(0, 'persian')) +$breed_data.add(Breed.new(1, 'siamese')) +$breed_data.add(Breed.new(2, 'sphinx')) +$breed_data.add(Breed.new(3, 'to_delete')) + ### CONTROLLERS class AuthorController < JSON::API::ResourceController @@ -144,6 +199,9 @@ class CurrenciesController < JSON::API::ResourceController class ExpenseEntriesController < JSON::API::ResourceController end +class BreedsController < JSON::API::ResourceController +end + ### RESOURCES class PersonResource < JSON::API::Resource attributes :id, :name, :email, :date_joined @@ -235,6 +293,26 @@ class ExpenseEntryResource < JSON::API::Resource has_one :employee end +class BreedResource < JSON::API::Resource + attributes :id, :name + + def self.find(attrs) + breeds = [] + $breed_data.breeds.values.each do |breed| + breeds.push(BreedResource.new(breed)) + end + breeds + end + + def self.find_by_key(id) + BreedResource.new($breed_data.breeds[id.to_i]) + end + + def self.transaction + yield + end +end + ### DATA javascript = Section.create(name: 'javascript') diff --git a/test/test_helper.rb b/test/test_helper.rb index ff0f4bdda..1e7e81daa 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -33,6 +33,7 @@ class TestApp < Rails::Application resources :tags resources :expense_entries resources :currencies, :param => :code + resources :breeds end class MiniTest::Unit::TestCase