diff --git a/README.md b/README.md index d08ea1d..dfcc4b2 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,8 @@ end class AlbumSerializer include RestPack::Serializer attributes :id, :title, :year, :artist_id, :extras + optional :score + can_include :artists, :songs can_filter_by :year @@ -78,6 +80,18 @@ end AlbumSerializer.as_json(album, { admin?: true }) ``` +All `attributes` are serialized by default. If you'd like to skip an attribute, you can pass an option in the `@context` as follows: + +```ruby +AlbumSerializer.as_json(album, { include_title?: false }) +``` + +You can also define `optional` attributes which aren't included by default. To include: + +```ruby +AlbumSerializer.as_json(album, { include_score?: true }) +``` + ## Exposing an API The `AlbumSerializer` provides `page` and `resource` methods which provide paged collection and singular resource GET endpoints. @@ -114,8 +128,7 @@ AlbumSerializer.page(params, Albums.where("year < 1950"), { admin?: true }) ``` Other features: - * [Dynamically Include/Exclude Attributes](https://github.com/RestPack/restpack_serializer/blob/master/spec/serializable/serializer_spec.rb#L42) - * [Custom Attributes Hash](https://github.com/RestPack/restpack_serializer/blob/master/spec/serializable/serializer_spec.rb#L46) + * [Custom Attributes Hash](https://github.com/RestPack/restpack_serializer/blob/master/spec/serializable/serializer_spec.rb#L55) ## Paging diff --git a/lib/restpack_serializer/serializable/attributes.rb b/lib/restpack_serializer/serializable/attributes.rb index 8bb19ec..321232e 100644 --- a/lib/restpack_serializer/serializable/attributes.rb +++ b/lib/restpack_serializer/serializable/attributes.rb @@ -14,6 +14,10 @@ def attributes(*attrs) attrs.each { |attr| attribute attr } end + def optional(*attrs) + attrs.each { |attr| optional_attribute attr } + end + def transform(attrs = [], transform_lambda) attrs.each { |attr| transform_attribute(attr, transform_lambda) } end @@ -34,6 +38,12 @@ def attribute(name, options={}) define_include_method name end + def optional_attribute(name, options={}) + add_to_serializable(name, options) + define_attribute_method name + define_optional_include_method name + end + def define_attribute_method(name) unless method_defined?(name) define_method name do @@ -45,12 +55,22 @@ def define_attribute_method(name) end end - def define_include_method(name) + def define_optional_include_method(name) + define_include_method(name, false) + end + + def define_include_method(name, include_by_default=true) method = "include_#{name}?".to_sym unless method_defined?(method) - define_method method do - @context[method].nil? || @context[method] + if include_by_default + define_method method do + @context[method].nil? || @context[method] + end + else + define_method method do + @context[method].present? + end end end end diff --git a/spec/serializable/attributes_spec.rb b/spec/serializable/attributes_spec.rb index b0f9556..b934643 100644 --- a/spec/serializable/attributes_spec.rb +++ b/spec/serializable/attributes_spec.rb @@ -4,6 +4,8 @@ class CustomSerializer include RestPack::Serializer attributes :a, :b, :c + attributes :d, :e + optional :sometimes, :maybe attribute :old_attribute, :key => :new_key transform [:gonzaga], lambda { |name, model| model.send(name).downcase } end @@ -11,11 +13,11 @@ class CustomSerializer subject(:attributes) { CustomSerializer.serializable_attributes } it "correctly models specified attributes" do - expect(attributes.length).to be(5) + expect(attributes.length).to be(9) end it "correctly maps normal attributes" do - [:a, :b, :c].each do |attr| + [:a, :b, :c, :d, :e].each do |attr| expect(attributes[attr]).to eq(attr) end end @@ -24,6 +26,26 @@ class CustomSerializer expect(attributes[:new_key]).to eq(:old_attribute) end + describe "optional attributes" do + let(:model) { OpenStruct.new(a: 'A', sometimes: 'SOMETIMES', gonzaga: 'GONZAGA') } + let(:context) { {} } + subject(:as_json) { CustomSerializer.as_json(model, context) } + + context 'with no includes context' do + it "excludes by default" do + expect(as_json[:sometimes]).to eq(nil) + end + end + + context 'with an includes context' do + let(:context) { { include_sometimes?: true } } + + it "allows then to be included" do + expect(as_json[:sometimes]).to eq('SOMETIMES') + end + end + end + describe '#transform_attributes' do let(:model) { OpenStruct.new(gonzaga: 'IS A SCHOOL') }