squares 0.3.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5881a376cfce79ade243a19dfa8f3a9b112d46e6
4
- data.tar.gz: 8530ebccc40ed950e0b1298d524d6e57da969149
3
+ metadata.gz: 61bb7be298dbe905a38099d2a09dca1556937426
4
+ data.tar.gz: bcab46fab1514b464f6f6dce30e5f42dfcc16e1c
5
5
  SHA512:
6
- metadata.gz: a16c5788667935c396834e0bac628ffee25259472e2a62025d19b38e6838bdfc0e7c047892ecb3e2a90a94f339d66b99dc18c4aa45dce6297b0e4226f1d43600
7
- data.tar.gz: a02e43ca8c0141b600c4a2c3840b5ce3d0960b0797226385d4b30f2e7c76d17865436021151bc8aefc53ed4b1a727523be02be857c3d1596ae6a327b4efa7103
6
+ metadata.gz: efb185b77ff2174e2f8d2823812e071cc48889120799dfe3466f01b81949528701f3aa35ffc839dd0a0b209cd94f0fab85d7cea61b5f612f99f075b07c735627
7
+ data.tar.gz: 680d476be0448c0f42ec6d54e390af3801e7b04a20ac94c905c2209d1801632bbada3e1180b7f05e0e23498201ce5d2a7320cb48363a733ce0e73fb1c7c23b2d
data/Guardfile ADDED
@@ -0,0 +1,83 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ ## Uncomment and set this to only include directories you want to watch
5
+ # directories %w(app lib config test spec features) \
6
+ # .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")}
7
+
8
+ ## Note: if you are using the `directories` clause above and you are not
9
+ ## watching the project directory ('.'), then you will want to move
10
+ ## the Guardfile to a watched dir and symlink it back, e.g.
11
+ #
12
+ # $ mkdir config
13
+ # $ mv Guardfile config/
14
+ # $ ln -s config/Guardfile .
15
+ #
16
+ # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
17
+
18
+ guard :bundler do
19
+ require 'guard/bundler'
20
+ require 'guard/bundler/verify'
21
+ helper = Guard::Bundler::Verify.new
22
+
23
+ files = ['Gemfile']
24
+ files += Dir['*.gemspec'] if files.any? { |f| helper.uses_gemspec?(f) }
25
+
26
+ # Assume files are symlinked from somewhere
27
+ files.each { |file| watch(helper.real_path(file)) }
28
+ end
29
+
30
+ # Note: The cmd option is now required due to the increasing number of ways
31
+ # rspec may be run, below are examples of the most common uses.
32
+ # * bundler: 'bundle exec rspec'
33
+ # * bundler binstubs: 'bin/rspec'
34
+ # * spring: 'bin/rspec' (This will use spring if running and you have
35
+ # installed the spring binstubs per the docs)
36
+ # * zeus: 'zeus rspec' (requires the server to be started separately)
37
+ # * 'just' rspec: 'rspec'
38
+
39
+ guard :rspec, cmd: "bundle exec rspec" do
40
+ require "guard/rspec/dsl"
41
+ dsl = Guard::RSpec::Dsl.new(self)
42
+
43
+ # Feel free to open issues for suggestions and improvements
44
+
45
+ # RSpec files
46
+ rspec = dsl.rspec
47
+ watch(rspec.spec_helper) { rspec.spec_dir }
48
+ watch(rspec.spec_support) { rspec.spec_dir }
49
+ watch(%r{^spec/fixtures/.*\.rb$}) { rspec.spec_dir }
50
+ watch(rspec.spec_files)
51
+
52
+ # Ruby files
53
+ ruby = dsl.ruby
54
+ dsl.watch_spec_files_for(ruby.lib_files)
55
+
56
+ # Rails files
57
+ rails = dsl.rails(view_extensions: %w(erb haml slim))
58
+ dsl.watch_spec_files_for(rails.app_files)
59
+ dsl.watch_spec_files_for(rails.views)
60
+
61
+ watch(rails.controllers) do |m|
62
+ [
63
+ rspec.spec.("routing/#{m[1]}_routing"),
64
+ rspec.spec.("controllers/#{m[1]}_controller"),
65
+ rspec.spec.("acceptance/#{m[1]}")
66
+ ]
67
+ end
68
+
69
+ # Rails config changes
70
+ watch(rails.spec_helper) { rspec.spec_dir }
71
+ watch(rails.routes) { "#{rspec.spec_dir}/routing" }
72
+ watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" }
73
+
74
+ # Capybara features specs
75
+ watch(rails.view_dirs) { |m| rspec.spec.("features/#{m[1]}") }
76
+ watch(rails.layouts) { |m| rspec.spec.("features/#{m[1]}") }
77
+
78
+ # Turnip features and steps
79
+ watch(%r{^spec/acceptance/(.+)\.feature$})
80
+ watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
81
+ Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance"
82
+ end
83
+ end
data/README.md CHANGED
@@ -47,10 +47,17 @@ You can also provide a default value if you switch to the `property` variant:
47
47
 
48
48
  ```ruby
49
49
  class Person < Squares::Base
50
- property :eye_color, default: 'brown'
50
+ property :irish?, default: false
51
+ property :eye_color, default: lambda { |person|
52
+ person.irish? ? 'green' : 'brown'
53
+ }
51
54
  end
52
55
  ```
53
56
 
57
+ Note that defaults which use callbacks (anything that responds to #call) are
58
+ always applied _after_ defaults which don't use callbacks. Reverse the order
59
+ of the above two properties, and their defaults would still work.
60
+
54
61
  ### Bootstrapping
55
62
 
56
63
  _A funny word for "setup & configure". Bootstrapping. Bootstrapping. See? Funny._
@@ -118,10 +125,25 @@ When we retrieve an object, it returns an instance of that model:
118
125
 
119
126
  ```ruby
120
127
  wallcrawler = Person['spiderman']
121
- wallcrawler = Person.find 'spiderman' #=> same, shmame.
122
- wallcrawler.id #=> 'spiderman'
123
- wallcrawler.real_name #=> 'Peter Parker'
124
- wallcrawler.class #=> Person
128
+ wallcrawler = Person.find 'spiderman' #=> same, shmame.
129
+ wallcrawler.class #=> Person
130
+ wallcrawler.id #=> 'spiderman'
131
+ ```
132
+
133
+ And then, of course, you can
134
+
135
+ ```ruby
136
+ wallcrawler.real_name #=> 'Peter Parker'
137
+ wallcrawler[:real_name] #=> 'Peter Parker'
138
+ wallcrawler.real_name = 'Petah Pahkah' #=> Boston's own
139
+ wallcrawler[:real_name] = 'Peshmerga Pete' #=> What is this, even?
140
+
141
+ wallcrawler.changed? #=> true
142
+ wallcrawler.save #=> now it's official
143
+
144
+ wallcrawler.update_properties( #=> also aliased as
145
+ real_name: 'Peter Parker' # #update_attributes
146
+ )
125
147
  ```
126
148
 
127
149
  Of course, for some types of storage, the model object has to be serialized and
@@ -129,7 +151,7 @@ de-serialized when it's stored and retrieved. Squares uses `Marshal.dump` and
129
151
  `Marshal.restore` to do that. This means that custom marshalling can be added
130
152
  to your models (see [documentation on ruby Marshal][marshal]).
131
153
 
132
- ### Even More Fun
154
+ ### _Where_-able Computing
133
155
 
134
156
  You can use the ActiveRecord-esque `.where` method with a block to retrieve records
135
157
  for which the block returns true:
@@ -138,13 +160,51 @@ for which the block returns true:
138
160
  Person.where { |p| p.left_handed == true } #=> all the lefties
139
161
  ```
140
162
 
141
- The `.where` method is actually just an alias of `.select`...which means, yeah!
142
- Squares are enumerable, yay!
163
+ In this mode, `.where` is essentially just like `.select`...which, oh yeah!
164
+ Squares are enumerable! Yay!
143
165
 
144
166
  ```ruby
145
167
  Person.map(&:name) #=> an array containing all the names
146
168
  ```
147
169
 
170
+ But you can also pass a hash to `.where` and it will do the expected thing:
171
+
172
+ ```ruby
173
+ Person.where( country: 'Latvaria' ) #=> Dr. Doom, I presume?
174
+ ```
175
+
176
+ And if you give `.where` a series of symbol arguments, it will consider them as
177
+ properties, and check the truthyness of each:
178
+
179
+ ```ruby
180
+ Person.where( :flying?, :secret_lair ) #=> Superman!
181
+ ```
182
+
183
+ ### Square Hooks
184
+
185
+ You can hang a callable (such as a Proc, a Lambda or an instance of class Batman
186
+ which implements `#call`) on any of Squares' polished, hand-crafted hooks:
187
+
188
+ ```ruby
189
+ class Hero
190
+ after_initialize do
191
+ tweet "In a world..."
192
+ end
193
+ end
194
+ ```
195
+
196
+ Squares supports the following hooks:
197
+
198
+ * after_initialize
199
+ * before/after_create
200
+ * after_find (e.g. after `.find` and also `.[]`)
201
+ * before/after_save
202
+ * before_destroy (`#delete` does not trigger this callback)
203
+
204
+ There are two important things to remember about Squares' hooks: 1) while a hooked
205
+ callback is in progress, no other hooks will be triggered (i.e. hooks can't fire
206
+ hooks), and 2) never feed Square Hooks after midnight.
207
+
148
208
  ### What It Doesn't Do
149
209
 
150
210
  Much like Wolverine, Squares doesn't do relationships. You'll have to
@@ -195,6 +255,19 @@ But hey, who cares, as long as yak hair is truthy?
195
255
 
196
256
  [marshal]:http://www.ruby-doc.org/core-2.1.5/Marshal.html
197
257
 
258
+ ## What's New in 0.3.0
259
+
260
+ * property defaults can use a callback
261
+ * implemented `#[]` on instances (to access properties)
262
+ * `#update_properties` (i.e. `#update_attributes`)
263
+ * square hooks
264
+ * `#changed?`
265
+ * `.where` accepts a hash, and/or series of properties
266
+
267
+ You can read in more detail on the [0.3.0 milestone][milestone]
268
+
269
+ [milestone]:https://github.com/joelhelbling/squares/issues?q=milestone%3A0.3.0+sort%3Acreated-asc
270
+
198
271
  ## Contributing
199
272
 
200
273
  1. Fork it ( https://github.com/joelhelbling/squares/fork )
data/Rakefile CHANGED
@@ -5,3 +5,12 @@ RSpec::Core::RakeTask.new(:spec)
5
5
 
6
6
  task :default => :spec
7
7
 
8
+ desc "a console for playing with Squares"
9
+ task :play do
10
+ require './lib/squares'
11
+ Dir['spec/fixtures/**/*.rb'].each do |fixture|
12
+ require File.expand_path(fixture)
13
+ end
14
+ require 'pry'
15
+ binding.pry
16
+ end
data/lib/squares.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'squares/version'
2
2
  require 'squares/base'
3
+ require 'json'
3
4
 
4
5
  module Squares
5
6
  class << self
data/lib/squares/base.rb CHANGED
@@ -3,14 +3,14 @@ module Squares
3
3
  attr_accessor :id
4
4
 
5
5
  def initialize *args
6
- apply *args
6
+ build_instance *args
7
7
  trigger :after_initialize
8
8
  end
9
9
 
10
10
  def save
11
11
  trigger :before_save
12
12
  @_changed = false
13
- store[@id] = Marshal.dump self.dup
13
+ store[@id] = serialize
14
14
  trigger :after_save
15
15
  nil
16
16
  end
@@ -52,13 +52,18 @@ module Squares
52
52
  self.class.properties
53
53
  end
54
54
 
55
+ def to_json(key_name = :id)
56
+ to_hash(key_name).to_json
57
+ end
58
+
55
59
  def to_h(key_name = :id)
56
- h = { key_name => id }
57
- properties.each do |property|
58
- h[property] = self.send(property)
60
+ { :type => self.class.name, key_name => id }.tap do |h|
61
+ properties.each do |property|
62
+ h[property] = self[property]
63
+ end
59
64
  end
60
- h
61
65
  end
66
+ alias_method :to_hash, :to_h
62
67
 
63
68
  def valid_property? property
64
69
  self.class.valid_property? property
@@ -68,8 +73,18 @@ module Squares
68
73
  self.class.defaults
69
74
  end
70
75
 
76
+ def serialize
77
+ serializers.inject(self.dup) do |memo, serializer|
78
+ serializer.dump memo
79
+ end
80
+ end
81
+
71
82
  private
72
83
 
84
+ def serializers
85
+ self.class.serializers
86
+ end
87
+
73
88
  def trigger hook_name
74
89
  return if @hook_callback_in_progress
75
90
  hooks = self.class.hooks
@@ -88,7 +103,7 @@ module Squares
88
103
  end
89
104
  end
90
105
 
91
- def apply *args
106
+ def build_instance *args
92
107
  @id, values = *args
93
108
  values_hash = values.to_h
94
109
  properties_sorted_by_defaults.each do |property|
@@ -141,9 +156,13 @@ module Squares
141
156
  class << self
142
157
  include Enumerable
143
158
 
159
+ def serializers
160
+ [Marshal]
161
+ end
162
+
144
163
  def [] id
145
164
  if item = store[id]
146
- Marshal.restore(item).tap do |item|
165
+ deserialize(item).tap do |item|
147
166
  item.instance_eval 'trigger :after_find'
148
167
  end
149
168
  end
@@ -182,7 +201,7 @@ module Squares
182
201
  end
183
202
 
184
203
  def values
185
- store.values.map{ |i| Marshal.restore i }
204
+ store.values.map{ |i| deserialize i }
186
205
  end
187
206
 
188
207
  def valid_property? property
@@ -273,7 +292,7 @@ module Squares
273
292
  end
274
293
 
275
294
  def models
276
- @_models.uniq.sort { |a,b| a.to_s <=> b.to_s }
295
+ (@_models || []).uniq.sort { |a,b| a.to_s <=> b.to_s }
277
296
  end
278
297
 
279
298
  ### hooks
@@ -328,6 +347,12 @@ module Squares
328
347
  @_models << subclass
329
348
  end
330
349
 
350
+ def deserialize item
351
+ serializers.reverse.inject(item) do |memo, serializer|
352
+ serializer.restore item
353
+ end
354
+ end
355
+
331
356
  end
332
357
  end
333
358
  end
@@ -1,3 +1,3 @@
1
1
  module Squares
2
- VERSION = "0.3.0"
2
+ VERSION = "0.3.1"
3
3
  end
@@ -0,0 +1,5 @@
1
+ module Marvel
2
+ class Sidekick < Squares::Base
3
+ property :catch_phrase
4
+ end
5
+ end
@@ -0,0 +1,8 @@
1
+ module Marvel
2
+ class SuperHero < Squares::Base
3
+ properties :real_name, :special_powers
4
+ property :caped?, default: false
5
+ end
6
+ end
7
+
8
+
@@ -0,0 +1,6 @@
1
+ module Marvel
2
+ class Villain < Squares::Base
3
+ properties :vehicle, :lair
4
+ property :really_evil?, default: true
5
+ end
6
+ end
data/spec/spec_helper.rb CHANGED
@@ -3,3 +3,8 @@ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
3
3
  require 'pry'
4
4
  require 'rspec-given'
5
5
  require 'squares'
6
+
7
+ # load fixtures
8
+ Dir['spec/fixtures/**/*.rb'].each do |fixture|
9
+ require File.expand_path(fixture)
10
+ end
@@ -1,20 +1,6 @@
1
1
  require 'spec_helper'
2
2
  require 'squares/base'
3
3
 
4
- module Marvel
5
- class SuperHero < Squares::Base
6
- properties :real_name, :special_powers
7
- property :caped?, default: false
8
- end
9
- class Villain < Squares::Base
10
- properties :vehicle, :lair
11
- property :really_evil?, default: true
12
- end
13
- class Sidekick < Squares::Base
14
- property :catch_phrase
15
- end
16
- end
17
-
18
4
  module Squares
19
5
  describe Base do
20
6
  Given(:storage) { {} }
@@ -205,18 +191,48 @@ module Squares
205
191
  describe '#to_h' do
206
192
  Given(:hero1) { test_class.new id, real_name: name, special_powers: powers }
207
193
  context 'default key name' do
208
- Given(:expected_hash) { { id: id, real_name: name, special_powers: powers, caped?: false } }
194
+ Given(:expected_hash) { { type: 'Marvel::SuperHero', id: id, real_name: name, special_powers: powers, caped?: false } }
209
195
  When(:result) { hero1.to_h }
210
196
  Then { expect(result).to eq(expected_hash) }
211
197
  end
212
198
 
213
199
  context 'custom key name' do
214
- Given(:expected_hash) { { hero: id, real_name: name, special_powers: powers, caped?: false } }
200
+ Given(:expected_hash) { { type: 'Marvel::SuperHero', hero: id, real_name: name, special_powers: powers, caped?: false } }
215
201
  When(:result) { hero1.to_h(:hero) }
216
202
  Then { expect(result).to eq(expected_hash) }
217
203
  end
218
204
  end
219
205
 
206
+ describe '#to_json' do
207
+ Given(:hero1) { test_class.new id, real_name: name, special_powers: powers }
208
+ context 'default key name' do
209
+ Given(:expected_json) do
210
+ {
211
+ type: 'Marvel::SuperHero',
212
+ id: id,
213
+ real_name: name,
214
+ special_powers: powers,
215
+ caped?: false
216
+ }.to_json
217
+ end
218
+ When(:result) { hero1.to_json }
219
+ Then { expect(result).to eq(expected_json) }
220
+ end
221
+ context 'custom key name' do
222
+ Given(:expected_json) do
223
+ {
224
+ type: 'Marvel::SuperHero',
225
+ hero: id,
226
+ real_name: name,
227
+ special_powers: powers,
228
+ caped?: false
229
+ }.to_json
230
+ end
231
+ When(:result) { hero1.to_json(:hero) }
232
+ Then { expect(result).to eq(expected_json) }
233
+ end
234
+ end
235
+
220
236
  describe '#==' do
221
237
  Given(:hero1) { test_class.new id, real_name: name, special_powers: powers }
222
238
  Given(:hero2) { test_class.new id, real_name: name, special_powers: powers }
data/squares.gemspec CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
10
10
  spec.email = ["joel@joelhelbling.com"]
11
11
  spec.summary = %q{Lightweight ORM backed by any hash-like storage. [*]}
12
12
  spec.description = %q{[*] Lightweight ORM backed by any hash-like storage: Redis, LevelDB or plain old hashes.}
13
- spec.homepage = "http://github.com/joelhelbling/squares"
13
+ spec.homepage = "http://squares.joelhelbling.com"
14
14
  spec.license = "MIT"
15
15
 
16
16
  spec.files = `git ls-files -z`.split("\x0")
@@ -23,4 +23,7 @@ Gem::Specification.new do |spec|
23
23
  spec.add_development_dependency "rspec"
24
24
  spec.add_development_dependency "rspec-given"
25
25
  spec.add_development_dependency "pry"
26
+ spec.add_development_dependency "guard"
27
+ spec.add_development_dependency "guard-bundler"
28
+ spec.add_development_dependency "guard-rspec"
26
29
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: squares
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joel Helbling
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-01-25 00:00:00.000000000 Z
11
+ date: 2016-02-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -80,6 +80,48 @@ dependencies:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: guard
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: guard-bundler
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: guard-rspec
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
83
125
  description: "[*] Lightweight ORM backed by any hash-like storage: Redis, LevelDB
84
126
  or plain old hashes."
85
127
  email:
@@ -92,17 +134,21 @@ files:
92
134
  - ".rspec"
93
135
  - ".travis.yml"
94
136
  - Gemfile
137
+ - Guardfile
95
138
  - LICENSE.txt
96
139
  - README.md
97
140
  - Rakefile
98
141
  - lib/squares.rb
99
142
  - lib/squares/base.rb
100
143
  - lib/squares/version.rb
144
+ - spec/fixtures/marvel/sidekick.rb
145
+ - spec/fixtures/marvel/super_hero.rb
146
+ - spec/fixtures/marvel/villain.rb
101
147
  - spec/spec_helper.rb
102
148
  - spec/squares/base_spec.rb
103
149
  - spec/squares_spec.rb
104
150
  - squares.gemspec
105
- homepage: http://github.com/joelhelbling/squares
151
+ homepage: http://squares.joelhelbling.com
106
152
  licenses:
107
153
  - MIT
108
154
  metadata: {}
@@ -127,6 +173,9 @@ signing_key:
127
173
  specification_version: 4
128
174
  summary: Lightweight ORM backed by any hash-like storage. [*]
129
175
  test_files:
176
+ - spec/fixtures/marvel/sidekick.rb
177
+ - spec/fixtures/marvel/super_hero.rb
178
+ - spec/fixtures/marvel/villain.rb
130
179
  - spec/spec_helper.rb
131
180
  - spec/squares/base_spec.rb
132
181
  - spec/squares_spec.rb