representable 1.3.2 → 1.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,4 @@ notifications:
3
3
  matrix:
4
4
  include:
5
5
  - rvm: 1.9.3
6
- gemfile: gemfiles/Gemfile.mongoid-2.4
7
- - rvm: 1.9.3
8
- gemfile: Gemfile
9
-
6
+
@@ -1,3 +1,8 @@
1
+ h2. 1.3.3
2
+
3
+ * Added new options: `:binding`, `:setter` and `:getter`.
4
+ * The `:if` option now eventually receives passed in user options.
5
+
1
6
  h2. 1.3.2
2
7
 
3
8
  * Some minor internal changes. Added `Config#inherit` to encasulate array push behavior.
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
- source :rubygems
1
+ source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
4
 
data/README.md CHANGED
@@ -172,6 +172,34 @@ For XML we just include the `Representable::XML` module.
172
172
  <composers>Sting</composers>
173
173
  </song>
174
174
 
175
+ ## Passing Options
176
+
177
+ You're free to pass an options hash into the rendering or parsing.
178
+
179
+ song.to_json(:append => "SOLD OUT!")
180
+
181
+ If you want to append the "SOLD OUT!" to the song's `title` when rendering, use the `:getter` option.
182
+
183
+ SongRepresenter
184
+ include Representable::JSON
185
+
186
+ property :title, :getter => lambda { |args| title + args[:append] }
187
+ end
188
+
189
+ Note that the block is executed in the represented model context which allows using accessors and instance variables.
190
+
191
+
192
+ The same works for parsing using the `:setter` method.
193
+
194
+ property :title, :setter => lambda { |val, args| self.title= val + args[:append] }
195
+
196
+ Here, the block retrieves two arguments: the parsed value and your user options.
197
+
198
+ You can also use the `:getter` option instead of writing a reader method. Even when you're not interested in user options you can still use this technique.
199
+
200
+ property :title, :getter => lambda { |*| @name }
201
+
202
+ This hash will also be available in the `:if` block, documented [here](https://github.com/apotonick/representable/#conditions) and will be passed to nested objects.
175
203
 
176
204
  ## Using Helpers
177
205
 
@@ -417,7 +445,6 @@ I do not recommend this approach as it bloats your domain classes with represent
417
445
 
418
446
  Here's a quick overview about other available options for `#property` and its bro `#collection`.
419
447
 
420
-
421
448
  ### Read/Write Restrictions
422
449
 
423
450
  Using the `:readable` and `:writeable` options access to properties can be restricted.
@@ -448,6 +475,14 @@ You can also define conditions on properties using `:if`, making them being cons
448
475
 
449
476
  When rendering or parsing, the `track` property is considered only if track is valid. Note that the block is executed in instance context, giving you access to instance methods.
450
477
 
478
+ As always, the block retrieves your options. Given this render call
479
+
480
+ song.to_json(minimum_track: 2)
481
+
482
+ your `:if` may process the options.
483
+
484
+ property :track, if: lambda { |opts| track > opts[:minimum_track] }
485
+
451
486
 
452
487
  ### False and Nil Values
453
488
 
@@ -476,6 +511,15 @@ Use the `:type` option to specify the conversion target. Note that `:default` st
476
511
  end
477
512
 
478
513
 
514
+ ## Undocumented Features
515
+
516
+ (Please don't read this section!)
517
+
518
+ If you need a special binding for a property you're free to create it using the `:binding` option.
519
+
520
+ property :title, :binding => lambda { |*args| JSON::TitleBinding.new(*args) }
521
+
522
+
479
523
  ## Copyright
480
524
 
481
525
  Representable started as a heavily simplified fork of the ROXML gem. Big thanks to Ben Woosley for his inspiring work.
data/Rakefile CHANGED
@@ -1,6 +1,4 @@
1
- require 'bundler'
2
- Bundler::GemHelper.install_tasks
3
-
1
+ require 'bundler/setup'
4
2
  require 'rake/testtask'
5
3
 
6
4
  desc 'Test the representable gem.'
@@ -11,3 +9,9 @@ Rake::TestTask.new(:test) do |test|
11
9
  test.test_files = FileList['test/*_test.rb']
12
10
  test.verbose = true
13
11
  end
12
+
13
+ Rake::TestTask.new(:test18) do |test|
14
+ test.libs << 'test'
15
+ test.test_files = FileList['test/*_test.rb'] - ['test/mongoid_test.rb', 'test/yaml_test.rb']
16
+ test.verbose = true
17
+ end
data/TODO CHANGED
@@ -14,4 +14,4 @@ document `XML::AttributeHash` etc
14
14
 
15
15
  * Song < OpenStruct in test_helper
16
16
 
17
- * have representable-options (:include, :exclude) and user-options
17
+ * have representable-options (:include, :exclude) and user-options
@@ -81,13 +81,18 @@ private
81
81
  end
82
82
 
83
83
  def skip_conditional_property?(binding)
84
+ # TODO: move to Binding.
84
85
  return unless condition = binding.options[:if]
85
- not instance_exec(&condition)
86
+
87
+ args = []
88
+ args << binding.user_options if condition.arity > 0 # TODO: remove arity check. users should know whether they pass options or not.
89
+
90
+ not instance_exec(*args, &condition)
86
91
  end
87
92
 
88
93
  # Retrieve value and write fragment to the doc.
89
94
  def compile_fragment(bin, doc)
90
- value = send(bin.getter)
95
+ value = bin.get
91
96
 
92
97
  bin.write_fragment(doc, value)
93
98
  end
@@ -95,17 +100,17 @@ private
95
100
  # Parse value from doc and update the model property.
96
101
  def uncompile_fragment(bin, doc)
97
102
  bin.read_fragment(doc) do |value|
98
- send(bin.setter, value)
103
+ bin.set(value)
99
104
  end
100
105
  end
101
-
106
+
102
107
  def representable_attrs
103
108
  @representable_attrs ||= self.class.representable_attrs # DISCUSS: copy, or better not?
104
109
  end
105
110
 
106
111
  def representable_bindings_for(format, options)
107
112
  options = cleanup_options(options) # FIXME: make representable-options and user-options two different hashes.
108
- representable_attrs.map {|attr| format.build_for(attr, self, options) }
113
+ representable_attrs.map {|attr| format.build(attr, self, options) }
109
114
  end
110
115
 
111
116
  # Returns the wrapper for the representation. Mostly used in XML.
@@ -5,6 +5,12 @@ module Representable
5
5
  class Binding < SimpleDelegator
6
6
  class FragmentNotFound
7
7
  end
8
+
9
+ def self.build(definition, *args)
10
+ # DISCUSS: move #create_binding to this class?
11
+ return definition.create_binding(*args) if definition.binding
12
+ build_for(definition, *args)
13
+ end
8
14
 
9
15
  def definition # TODO: remove in 1.4.
10
16
  raise "Binding#definition is no longer supported as all Definition methods are now delegated automatically."
@@ -15,6 +21,8 @@ module Representable
15
21
  @represented = represented
16
22
  @user_options = user_options
17
23
  end
24
+
25
+ attr_reader :user_options, :represented # TODO: make private/remove.
18
26
 
19
27
  # Main entry point for rendering/parsing a property object.
20
28
  def serialize(value)
@@ -51,6 +59,16 @@ module Representable
51
59
  def read_fragment_for(doc)
52
60
  read(doc)
53
61
  end
62
+
63
+ def get
64
+ return represented.instance_exec(user_options, &options[:getter]) if options[:getter]
65
+ represented.send(getter)
66
+ end
67
+
68
+ def set(value)
69
+ value = represented.instance_exec(value, user_options, &options[:setter]) if options[:setter]
70
+ represented.send(setter, value)
71
+ end
54
72
 
55
73
 
56
74
  # Hooks into #serialize and #deserialize to extend typed properties
@@ -57,4 +57,4 @@ module Representable
57
57
  end
58
58
  end
59
59
  end
60
- end
60
+ end
@@ -63,5 +63,13 @@ module Representable
63
63
  def default
64
64
  options[:default]
65
65
  end
66
+
67
+ def binding
68
+ options[:binding]
69
+ end
70
+
71
+ def create_binding(*args)
72
+ binding.call(self, *args)
73
+ end
66
74
  end
67
75
  end
@@ -1,3 +1,3 @@
1
1
  module Representable
2
- VERSION = "1.3.2"
2
+ VERSION = "1.3.3"
3
3
  end
@@ -25,7 +25,7 @@ Gem::Specification.new do |s|
25
25
  s.add_development_dependency "rake"
26
26
  s.add_development_dependency "test_xml"
27
27
  s.add_development_dependency "minitest", ">= 2.8.1"
28
- s.add_development_dependency "mocha"
28
+ s.add_development_dependency "mocha", ">= 0.13.0"
29
29
  s.add_development_dependency "mongoid"
30
30
  s.add_development_dependency "virtus", "~> 0.5.0"
31
31
  end
@@ -181,6 +181,23 @@ class DefinitionTest < MiniTest::Spec
181
181
 
182
182
  end
183
183
 
184
+ describe "#binding" do
185
+ it "returns true when :binding is set" do
186
+ assert Representable::Definition.new(:songs, :binding => Object).binding
187
+ end
188
+
189
+ it "returns false when :binding is not set" do
190
+ assert !Representable::Definition.new(:songs).binding
191
+ end
192
+ end
193
+
194
+ describe "#create_binding" do
195
+ it "executes the block (without special context)" do
196
+ definition = Representable::Definition.new(:title, :binding => lambda { |*args| @binding = Representable::Binding.new(*args) })
197
+ definition.create_binding(object=Object.new).must_equal @binding
198
+ @binding.instance_variable_get(:@represented).must_equal object
199
+ end
200
+ end
184
201
 
185
202
  describe ":collection => true" do
186
203
  before do
@@ -232,4 +249,14 @@ class DefinitionTest < MiniTest::Spec
232
249
  assert ! Representable::Definition.new(:songs).hash?
233
250
  end
234
251
  end
252
+
253
+ describe ":binding => Object" do
254
+ subject do
255
+ Representable::Definition.new(:songs, :binding => Object)
256
+ end
257
+
258
+ it "responds to #binding" do
259
+ assert_equal subject.binding, Object
260
+ end
261
+ end
235
262
  end
@@ -444,7 +444,6 @@ class RepresentableTest < MiniTest::Spec
444
444
  assert_equal nil, band.fame
445
445
  end
446
446
 
447
-
448
447
  it "executes block in instance context" do
449
448
  @pop.class_eval { property :fame, :if => lambda { groupies } }
450
449
  band = @pop.new
@@ -452,6 +451,39 @@ class RepresentableTest < MiniTest::Spec
452
451
  band.update_properties_from({"fame"=>"oh yes"}, {}, Representable::Hash::PropertyBinding)
453
452
  assert_equal "oh yes", band.fame
454
453
  end
454
+
455
+ describe "propagating user options to the block" do
456
+ representer! do
457
+ property :name, :if => lambda { |opts| opts[:include_name] }
458
+ end
459
+ subject { OpenStruct.new(:name => "Outbound").extend(representer) }
460
+
461
+ it "works without specifying options" do
462
+ subject.to_hash.must_equal({})
463
+ end
464
+
465
+ it "passes user options to block" do
466
+ subject.to_hash(:include_name => true).must_equal({"name" => "Outbound"})
467
+ end
468
+ end
469
+ end
470
+
471
+ describe ":getter and :setter" do
472
+ representer! do
473
+ property :name,
474
+ :getter => lambda { |args| "#{args[:welcome]} #{name}" },
475
+ :setter => lambda { |val, args| self.name = "#{args[:welcome]} #{val}" }
476
+ end
477
+
478
+ subject { OpenStruct.new(:name => "Mony Mony").extend(representer) }
479
+
480
+ it "uses :getter when rendering" do
481
+ subject.to_hash(:welcome => "Hi").must_equal({"name" => "Hi Mony Mony"})
482
+ end
483
+
484
+ it "uses :setter when parsing" do
485
+ subject.from_hash({"name" => "Eyes Without A Face"}, :welcome => "Hello").name.must_equal "Hello Eyes Without A Face"
486
+ end
455
487
  end
456
488
 
457
489
  describe ":extend and :class" do
@@ -567,6 +599,21 @@ class RepresentableTest < MiniTest::Spec
567
599
  end
568
600
  end
569
601
  end
602
+
603
+ describe ":binding" do
604
+ representer! do
605
+ class MyBinding < Representable::Binding
606
+ def write(doc, *args)
607
+ doc[:title] = @represented.title
608
+ end
609
+ end
610
+ property :title, :binding => lambda { |*args| MyBinding.new(*args) }
611
+ end
612
+
613
+ it "uses the specified binding instance" do
614
+ OpenStruct.new(:title => "Affliction").extend(representer).to_hash.must_equal({:title => "Affliction"})
615
+ end
616
+ end
570
617
 
571
618
  end
572
619
 
@@ -1,7 +1,3 @@
1
- require 'bundler'
2
- Bundler.setup
3
-
4
- gem 'minitest'
5
1
  require 'representable'
6
2
  require 'representable/json'
7
3
  require 'representable/xml'
@@ -9,7 +5,7 @@ require 'test/unit'
9
5
  require 'minitest/spec'
10
6
  require 'minitest/autorun'
11
7
  require 'test_xml/mini_test'
12
- require 'mocha'
8
+ require 'mocha/setup'
13
9
 
14
10
  class Album
15
11
  attr_accessor :songs, :best_song
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: representable
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.2
4
+ version: 1.3.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-02-12 00:00:00.000000000 Z
12
+ date: 2013-02-15 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: nokogiri
@@ -98,7 +98,7 @@ dependencies:
98
98
  requirements:
99
99
  - - ! '>='
100
100
  - !ruby/object:Gem::Version
101
- version: '0'
101
+ version: 0.13.0
102
102
  type: :development
103
103
  prerelease: false
104
104
  version_requirements: !ruby/object:Gem::Requirement
@@ -106,7 +106,7 @@ dependencies:
106
106
  requirements:
107
107
  - - ! '>='
108
108
  - !ruby/object:Gem::Version
109
- version: '0'
109
+ version: 0.13.0
110
110
  - !ruby/object:Gem::Dependency
111
111
  name: mongoid
112
112
  requirement: !ruby/object:Gem::Requirement
@@ -154,7 +154,6 @@ files:
154
154
  - README.md
155
155
  - Rakefile
156
156
  - TODO
157
- - gemfiles/Gemfile.mongoid-2.4
158
157
  - lib/representable.rb
159
158
  - lib/representable/binding.rb
160
159
  - lib/representable/bindings/hash_bindings.rb
@@ -1,6 +0,0 @@
1
- source "http://rubygems.org"
2
-
3
- # Specify your gem's dependencies in roar-rails.gemspec
4
- gemspec :path => '../'
5
-
6
- gem 'mongoid', '~> 2.4.0'