representable 1.3.2 → 1.3.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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'