Skip to content

Instantly share code, notes, and snippets.

@Veejay
Created March 23, 2012 01:17
Show Gist options
  • Save Veejay/2165970 to your computer and use it in GitHub Desktop.
Save Veejay/2165970 to your computer and use it in GitHub Desktop.
JSON Serializer built on top of Jbuilder with emphasis on ease of use
module StaffPlan::JsonSerializer
def serialize(*args)
options = args.extract_options!
if args.first.is_a?(Array) or args.first.is_a?(ActiveRecord::Relation)
serialize_collection(args.first, options)
else
serialize_instance(args.first, options)
end
end
private
def serialize_instance(*args)
options = args.extract_options!
instance = args.first
Jbuilder.encode do |json|
json.extract! args.first, *filter_attributes(instance, options)
options.except(:only, :except).each do |k,v|
json.__send__ k.to_sym, ActiveSupport::JSON.decode(serialize(instance.send(k.to_sym), v))
end
end
end
def serialize_collection(*args)
options = args.extract_options!
collection = case
when args.first.is_a?(ActiveRecord::Relation)
args.first.all
when args.first.is_a?(Array)
args.first
end
Jbuilder.encode do |json|
json.array! collection do |json, instance|
json.extract! instance, *filter_attributes(instance, options)
options.except(:only, :except).each do |k,v|
json.__send__ k.to_sym, ActiveSupport::JSON.decode(serialize(instance.send(k.to_sym), v))
end
end
end
end
def filter_attributes(*args)
options = args.extract_options!
attributes = args.first.attributes.keys.map(&:to_sym)
timestamps = [:updated_at, :created_at]
case
when options.has_key?(:only)
options[:only]
when options.has_key?(:except)
attributes - options[:except]
else
args.first.attributes.keys.map(&:to_sym)
end - timestamps
end
end
@Veejay
Copy link
Author

Veejay commented Mar 23, 2012

What works well:

Nested relations with only as an option. Meaning you can get something like

serialize(Author.first, only: [:first_name, :last_name, :email], posts: {only: [:title, :content], comments: {only: [:title, ;content]}})

What would be nice to have:

  • I'd like to avoid having to decode the JSON produced by the recursive calls.
  • Adding an except option as well (blacklist instead of whitelist)
  • Have a sensible default for when the user doesn't specify the fields he/she's interested in. Probably something like (attributes.keys - %w(updated_at created_at)).map(&:to_sym) that I could then pass to extract!
  • Support scoping for more complex chaining (i.e. if A HABTM B and B has_many C, I'd like to be able to filter the instances of C that belong to a given instance of A)
  • Find a way to not encode anything in the body of the recursive methods and only call .target! later on. Something like foo = serialize ...; foo.target!

@Veejay
Copy link
Author

Veejay commented Mar 23, 2012

  • Added support for except and all attributes
  • Found out how to actually send a message to the builder. Turns out you need to send since Jbuilder inherits from BlankSlate.
  • Made the two delegate methods and the convenience method filter_attributes private.

Still needs to be addressed:

  • Don't like the case when in filter_attributes, it's an eye sore
  • It's stupid to encode and decode all the time throughout the method (and it has a cost too)
  • Support for scoping... Maybe with a syntax à la serialize(@user, except: [:id], projects: {only: [:name, :active], work_weeks: {for: {:user, @user.id}, only: [:foobar]}}) or something...
  • Correct spelling mistakes automatically (typing project instead of projects) can be corrected through reflection on the association. Nice to have in the future.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment