squares 0.3.0 → 0.3.1

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.
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