diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..0917974 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,15 @@ +# Lines starting with '#' are comments. +# Each line is a file pattern followed by one or more owners. + +# More details are here: https://help.github.com/articles/about-codeowners/ + +# The '*' pattern is global owners. + +# Order is important. The last matching pattern has the most precedence. +# The folders are ordered as follows: + +# In each subsection folders are ordered first by depth, then alphabetically. +# This should make it easy to add new rules without breaking existing ones. + +# Global rule: +* @jho406 diff --git a/README.md b/README.md index 0e88c43..745575c 100644 --- a/README.md +++ b/README.md @@ -93,8 +93,7 @@ You can also add a [layout](#layouts). ### json.set! or json.\ -Defines the attribute or structure. All keys are automatically camelized lower -by default. See [Change Key Format](#change-key-format) to change this behavior. +Defines the attribute or structure. All keys are not formatted by default. See [Change Key Format](#change-key-format) to change this behavior. ```ruby json.set! :authorDetails, {...options} do @@ -156,6 +155,33 @@ The difference between the block form and inline form is 2. The inline form is considered a leaf node, and you can only [search](#traversing) for internal nodes. +### json.extract! +Extracts attributes from object or hash in 1 line + +```ruby +# without extract! +json.id user.id +json.email user.email +json.firstName user.first_name + +# with extract! +json.extract! user, :id, :email, :first_name + +# => {"id" => 1, "email" => "email@gmail.com", "first_name" => "user"} + +# with extract! with key transformation +json.extract! user, :id, [:first_name, :firstName], [:last_name, :lastName] + +# => {"id" => 1, "firstName" => "user", "lastName" => "last"} +``` + +The inline form defines object and attributes + +| Parameter | Notes | +| :--- | :--- | +| object | An object | +| attributes | A list of attributes | + ### json.array! Generates an array of json objects. @@ -291,7 +317,7 @@ end ### Partials Partials are supported. The following will render the file -`views/posts/_blog_posts.json.props`, and set a local variable `foo` assigned +`views/posts/_blog_posts.json.props`, and set a local variable `post` assigned with @post, which you can use inside the partial. ```ruby @@ -303,6 +329,7 @@ Usage with arrays: ```ruby # The `as:` option is supported when using `array!` +# Without `as:` option you can use blog_post variable (name is based on partial's name) inside partial json.posts do json.array! @posts, partial: ["posts/blog_post", locals: {foo: 'bar'}, as: 'post'] do @@ -592,15 +619,60 @@ json.flash flash.to_h will render Layout first, then the template when `yield json` is used. ## Change key format -By default, keys are not formatted. If you want to change this behavior, -override it in an initializer: +By default, keys are not formatted. This is intentional. By being explicity with your keys, +it makes your views quicker and more easily searchable when working in Javascript land. + +If you must change this behavior, override it in an initializer and cache the value: ```ruby +# default behavior Props::BaseWithExtensions.class_eval do + # json.firstValue "first" + # json.second_value "second" + # + # -> { "firstValue" => "first", "second_value" => "second" } def key_format(key) key.to_s end end + +# camelCased behavior +Props::BaseWithExtensions.class_eval do + # json.firstValue "first" + # json.second_value "second" + # + # -> { "firstValue" => "first", "secondValue" => "second" } + def key_format(key) + @key_cache ||= {} + @key_cache[key] ||= key.to_s.camelize(:lower) + @key_cache[key] + end + + def result! + result = super + @key_cache = {} + result + end +end + +# snake_cased behavior +Props::BaseWithExtensions.class_eval do + # json.firstValue "first" + # json.second_value "second" + # + # -> { "first_value" => "first", "second_value" => "second" } + def key_format(key) + @key_cache ||= {} + @key_cache[key] ||= key.to_s.underscore + @key_cache[key] + end + + def result! + result = super + @key_cache = {} + result + end +end ``` ## Escape mode diff --git a/lib/props_template.rb b/lib/props_template.rb index 5b2f259..9016637 100644 --- a/lib/props_template.rb +++ b/lib/props_template.rb @@ -23,6 +23,7 @@ class << self delegate :result!, :array!, :partial!, + :extract!, :deferred!, :fragments!, :set_block_content!, diff --git a/lib/props_template/base.rb b/lib/props_template/base.rb index 18df252..3fcf673 100644 --- a/lib/props_template/base.rb +++ b/lib/props_template/base.rb @@ -109,6 +109,28 @@ def partial!(**options) @context.render options end + # json.id item.id + # json.value item.value + # + # json.extract! item, :id, :value + # + # with key transformation + # json.extract! item, :id, [:first_name, :firstName] + def extract!(object, *values) + values.each do |value| + key, attribute = if value.is_a?(Array) + [value[1], value[0]] + else + [value, value] + end + + set!( + key, + object.is_a?(Hash) ? object.fetch(attribute) : object.public_send(attribute) + ) + end + end + def result! if @scope.nil? @stream.push_object diff --git a/spec/props_template_spec.rb b/spec/props_template_spec.rb index f3988d6..b9fd6f6 100644 --- a/spec/props_template_spec.rb +++ b/spec/props_template_spec.rb @@ -356,4 +356,53 @@ ]) end end + + context "extract!" do + it "extracts values for hash" do + object = { :foo => "bar", "bar" => "foo", :wiz => "wiz" } + + json = Props::Base.new + json.extract! object, :foo, "bar" + attrs = json.result!.strip + + expect(attrs).to eql_json({ + foo: "bar", + bar: "foo" + }) + end + + it "extracts values for hash with key transformation" do + object = { :foo => "bar", "bar_bar" => "foo", :wiz => "wiz" } + + json = Props::Base.new + json.extract! object, :foo, ["bar_bar", "barBar"] + attrs = json.result!.strip + + expect(attrs).to eql_json({ + foo: "bar", + barBar: "foo" + }) + end + + it "extracts values for object" do + class FooBar + def foo + "bar" + end + + def bar + "foo" + end + end + + json = Props::Base.new + json.extract! FooBar.new, :foo, "bar" + attrs = json.result!.strip + + expect(attrs).to eql_json({ + foo: "bar", + bar: "foo" + }) + end + end end