embedded_record 0.0.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.
- data/.gitignore +5 -0
- data/Gemfile +4 -0
- data/README.md +105 -0
- data/Rakefile +8 -0
- data/embedded_record.gemspec +23 -0
- data/lib/embedded_record.rb +201 -0
- data/test/embedded_record_test.rb +139 -0
- metadata +53 -0
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
# EmbeddedRecord
|
2
|
+
|
3
|
+
* http://github.com/wojtekmach/embedded\_record
|
4
|
+
|
5
|
+
## DESCRIPTION:
|
6
|
+
|
7
|
+
EmbeddedRecord is designed to do two things:
|
8
|
+
|
9
|
+
* Define records on a Class
|
10
|
+
* Embed one or many records (similiar to belongs\_to and
|
11
|
+
has\_and\_belongs\_to\_many associations)
|
12
|
+
|
13
|
+
## SYNOPSIS:
|
14
|
+
|
15
|
+
Basic embeddable record usage:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
class Color
|
19
|
+
include EmbeddedRecord::Record
|
20
|
+
attribute :name
|
21
|
+
attribute :hex
|
22
|
+
|
23
|
+
record :red, :name => "Red", :hex => "#FF0000"
|
24
|
+
record :green, :name => "Green", :hex => "#00FF00"
|
25
|
+
record :blue, :name => "Blue", :hex => "#0000FF"
|
26
|
+
end
|
27
|
+
|
28
|
+
p Color.ids # => [:red, :green, :blue]
|
29
|
+
p Color.all # => [#<Color>, #<Color>, #<Color>]
|
30
|
+
Color.first
|
31
|
+
Color.last
|
32
|
+
|
33
|
+
blue = Color.find(:blue)
|
34
|
+
puts blue.id # => :blue
|
35
|
+
|
36
|
+
p blue.index # => 2
|
37
|
+
p blue.name # => "Blue"
|
38
|
+
p blue.hex # => "#0000FF"
|
39
|
+
|
40
|
+
p blue.is?(:red) # false
|
41
|
+
```
|
42
|
+
|
43
|
+
Embedding record:
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
class Car
|
47
|
+
attr_accessor :color_mask # integer id of a record
|
48
|
+
include EmbeddedRecord
|
49
|
+
embed_record :color, :class => Color
|
50
|
+
end
|
51
|
+
|
52
|
+
car = Car.new
|
53
|
+
car.color_id = :red
|
54
|
+
p car.color_mask # => 0
|
55
|
+
p car.color.name # => "Red"
|
56
|
+
```
|
57
|
+
|
58
|
+
Embedding records:
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
class Car
|
62
|
+
attr_accessor :colors_mask # integer bitmask of record ids
|
63
|
+
include EmbeddedRecord
|
64
|
+
embed_records :colors, :class => Color, :singular => :color
|
65
|
+
end
|
66
|
+
|
67
|
+
car = Car.new
|
68
|
+
car.color_ids = [:red, :green]
|
69
|
+
p car.colors.first.name # => "Red"
|
70
|
+
p car.color_ids # => [:red, :green]
|
71
|
+
p car.colors_mask # => 3 (2**0 + 2**1)
|
72
|
+
```
|
73
|
+
|
74
|
+
## REQUIREMENTS:
|
75
|
+
|
76
|
+
None
|
77
|
+
|
78
|
+
## INSTALL:
|
79
|
+
|
80
|
+
* `gem install embedded\_record`
|
81
|
+
|
82
|
+
## LICENSE:
|
83
|
+
|
84
|
+
(The MIT License)
|
85
|
+
|
86
|
+
Copyright (c) 2011 Wojciech Mach
|
87
|
+
|
88
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
89
|
+
a copy of this software and associated documentation files (the
|
90
|
+
'Software'), to deal in the Software without restriction, including
|
91
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
92
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
93
|
+
permit persons to whom the Software is furnished to do so, subject to
|
94
|
+
the following conditions:
|
95
|
+
|
96
|
+
The above copyright notice and this permission notice shall be
|
97
|
+
included in all copies or substantial portions of the Software.
|
98
|
+
|
99
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
100
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
101
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
102
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
103
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
104
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
105
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "embedded_record"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "embedded_record"
|
7
|
+
s.version = EmbeddedRecord::VERSION
|
8
|
+
s.authors = ["Wojciech Mach"]
|
9
|
+
s.email = ["wojtek@wojtekmach.pl"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{Embed objects in a bitmask field. Similar to bitmask-attribute and friends}
|
12
|
+
|
13
|
+
s.rubyforge_project = "embedded_record"
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
17
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
|
20
|
+
# specify any dependencies here; for example:
|
21
|
+
# s.add_development_dependency "rspec"
|
22
|
+
# s.add_runtime_dependency "rest-client"
|
23
|
+
end
|
@@ -0,0 +1,201 @@
|
|
1
|
+
module EmbeddedRecord
|
2
|
+
VERSION = "0.0.1"
|
3
|
+
|
4
|
+
def self.included(klass)
|
5
|
+
klass.extend self
|
6
|
+
end
|
7
|
+
|
8
|
+
##
|
9
|
+
# Options:
|
10
|
+
# - :class - Class to embed
|
11
|
+
# - :scope - Boolean wheter to install ActiveRecord scope
|
12
|
+
def embed_record(name, options = {})
|
13
|
+
raise ArgumentError, "Option :class is required" unless options[:class]
|
14
|
+
|
15
|
+
klass = options[:class]
|
16
|
+
attr = "#{name}_mask"
|
17
|
+
all = klass.all
|
18
|
+
|
19
|
+
define_method name do
|
20
|
+
if val = send(attr)
|
21
|
+
klass.all[val]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
define_method "#{name}_id" do
|
26
|
+
if val = send(attr)
|
27
|
+
klass.all[val].id
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
define_method "#{name}_id=" do |id|
|
32
|
+
type_method = embed_id_type_method(klass)
|
33
|
+
index = klass.all.index { |obj| obj.id == id.send(type_method) }
|
34
|
+
send "#{attr}=", index
|
35
|
+
index
|
36
|
+
end
|
37
|
+
|
38
|
+
if options[:scope] == true
|
39
|
+
embed_record_scope name
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
##
|
44
|
+
# Options:
|
45
|
+
# - class - Class of record
|
46
|
+
# - singular - singular form of name
|
47
|
+
#
|
48
|
+
# Example:
|
49
|
+
#
|
50
|
+
# class Shirt
|
51
|
+
# embed_records :colors, :class => Color, :singular => :color
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
def embed_records(name, options = {})
|
55
|
+
[:class, :singular].each do |opt|
|
56
|
+
raise ArgumentError, "Option :#{opt} is required" unless options[opt]
|
57
|
+
end
|
58
|
+
|
59
|
+
singular = options[:singular]
|
60
|
+
klass = options[:class]
|
61
|
+
all_ids = klass.all.map { |obj| obj.id }
|
62
|
+
attr = "#{name}_mask"
|
63
|
+
|
64
|
+
define_method "#{singular}_ids=" do |ids|
|
65
|
+
type_method = embed_id_type_method(klass)
|
66
|
+
ids = ids.map(&type_method)
|
67
|
+
self.send "#{attr}=",
|
68
|
+
(ids & all_ids).map { |r| 2**all_ids.index(r) }.inject(0, :+)
|
69
|
+
end
|
70
|
+
|
71
|
+
define_method "#{singular}_ids" do
|
72
|
+
all_ids.reject { |r| ((send(attr) || 0) & 2**all_ids.index(r)).zero? }
|
73
|
+
end
|
74
|
+
|
75
|
+
define_method name do
|
76
|
+
ids = send("#{singular}_ids")
|
77
|
+
|
78
|
+
klass.all.select do |obj|
|
79
|
+
ids.include? obj.id
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
if options[:scope] == true
|
84
|
+
embed_records_scope name
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def embed_record_scope(name)
|
89
|
+
klass = name.to_s.classify.constantize
|
90
|
+
plural = name.to_s.pluralize
|
91
|
+
|
92
|
+
send :scope, :"with_#{name.to_s.tableize}", lambda { |*ids|
|
93
|
+
masks = ids.map { |id| klass.find(id).index }
|
94
|
+
where("#{plural}_mask in (?)", masks)
|
95
|
+
}
|
96
|
+
end
|
97
|
+
|
98
|
+
def embed_records_scope(name)
|
99
|
+
klass = name.to_s.classify.constantize
|
100
|
+
plural = name.to_s.pluralize
|
101
|
+
|
102
|
+
send :scope, :"with_#{name.to_s.tableize}", lambda { |*ids|
|
103
|
+
masks = ids.map { |id| 2 ** klass.find(id).index }.join(" | ")
|
104
|
+
where("#{plural}_mask & (?)", masks)
|
105
|
+
}
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def embed_id_type_method(klass)
|
111
|
+
klass.ids.first.is_a?(Symbol) ? :to_sym : :to_s
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
module EmbeddedRecord::Record
|
116
|
+
def self.included(klass)
|
117
|
+
klass.extend ClassMethods
|
118
|
+
klass.attribute :id
|
119
|
+
end
|
120
|
+
|
121
|
+
##
|
122
|
+
# Returns true if record's id is the same as argument or false otherwise
|
123
|
+
def is?(id)
|
124
|
+
self.id == id
|
125
|
+
end
|
126
|
+
|
127
|
+
##
|
128
|
+
# Returns Integer index of the record in the records set
|
129
|
+
def index
|
130
|
+
self.class.all.index(self)
|
131
|
+
end
|
132
|
+
|
133
|
+
module ClassMethods
|
134
|
+
##
|
135
|
+
# Returns Array of record attributes
|
136
|
+
def attributes
|
137
|
+
@attributes ||= []
|
138
|
+
end
|
139
|
+
|
140
|
+
##
|
141
|
+
# Returns Array of records ids
|
142
|
+
def ids
|
143
|
+
records.keys
|
144
|
+
end
|
145
|
+
|
146
|
+
##
|
147
|
+
# Returns record for a given id or nil
|
148
|
+
def find(id)
|
149
|
+
all.find { |obj| obj.id == id }
|
150
|
+
end
|
151
|
+
|
152
|
+
##
|
153
|
+
# Returns all records
|
154
|
+
def all
|
155
|
+
records.values
|
156
|
+
end
|
157
|
+
|
158
|
+
##
|
159
|
+
# Returns first record
|
160
|
+
def first
|
161
|
+
all.first
|
162
|
+
end
|
163
|
+
|
164
|
+
##
|
165
|
+
# Returns last record
|
166
|
+
def last
|
167
|
+
all.last
|
168
|
+
end
|
169
|
+
|
170
|
+
##
|
171
|
+
# Defines a record with an id and Hash of attributes.
|
172
|
+
def record(id, attrs = {})
|
173
|
+
record = new
|
174
|
+
record.send "id=", id
|
175
|
+
attrs.each do |k, v|
|
176
|
+
unless attributes.include? k
|
177
|
+
raise ArgumentError, "Atrribute '#{k}' not found"
|
178
|
+
end
|
179
|
+
|
180
|
+
record.send "#{k}=", v
|
181
|
+
end
|
182
|
+
records[id] = record
|
183
|
+
end
|
184
|
+
|
185
|
+
##
|
186
|
+
# Defines a Symbol attribute for a record
|
187
|
+
def attribute(name)
|
188
|
+
attributes << name
|
189
|
+
|
190
|
+
unless method_defined?(name)
|
191
|
+
attr_accessor name
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
private
|
196
|
+
|
197
|
+
def records
|
198
|
+
@records ||= {}
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
gem "minitest"
|
2
|
+
require "minitest/spec"
|
3
|
+
require "minitest/autorun"
|
4
|
+
require "embedded_record"
|
5
|
+
|
6
|
+
class Color
|
7
|
+
include EmbeddedRecord::Record
|
8
|
+
|
9
|
+
attribute :name
|
10
|
+
|
11
|
+
record :red, :name => "Red"
|
12
|
+
record :green, :name => "Green"
|
13
|
+
record :blue, :name => "Blue"
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "Object with EmbeddedRecord::Record" do
|
17
|
+
it "#is? returns true if id is the same" do
|
18
|
+
Color.find(:green).is?(:green).must_equal true
|
19
|
+
end
|
20
|
+
|
21
|
+
it "#is? returns false if id is not the same" do
|
22
|
+
Color.find(:green).is?(:red).must_equal false
|
23
|
+
end
|
24
|
+
|
25
|
+
it "#index return record index in record set" do
|
26
|
+
Color.find(:red).index.must_equal 0
|
27
|
+
Color.find(:green).index.must_equal 1
|
28
|
+
Color.find(:blue).index.must_equal 2
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "Class with EmbeddedRecord::Record" do
|
33
|
+
it "::attributes returns names of record attributes" do
|
34
|
+
Color.attributes.must_equal [:id, :name]
|
35
|
+
end
|
36
|
+
|
37
|
+
it "::all returns all records" do
|
38
|
+
all = Color.all
|
39
|
+
all.must_be_kind_of Array
|
40
|
+
all.size.must_equal 3
|
41
|
+
all.first.must_be_kind_of Color
|
42
|
+
all.first.name.must_equal "Red"
|
43
|
+
end
|
44
|
+
|
45
|
+
it "::ids returns records ids" do
|
46
|
+
Color.ids.must_equal [:red, :green, :blue]
|
47
|
+
end
|
48
|
+
|
49
|
+
it "::find returns record with specified id" do
|
50
|
+
Color.find(:green).id.must_equal :green
|
51
|
+
end
|
52
|
+
|
53
|
+
it "::find returns nil when no record is found for an id" do
|
54
|
+
Color.find(:bad).must_equal nil
|
55
|
+
end
|
56
|
+
|
57
|
+
it "::first returns first record" do
|
58
|
+
Color.first.id.must_equal :red
|
59
|
+
end
|
60
|
+
|
61
|
+
it "::last returns last record" do
|
62
|
+
Color.last.id.must_equal :blue
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe "Class with EmbeddedRecord" do
|
67
|
+
before do
|
68
|
+
@cls = Class.new
|
69
|
+
@cls.send :include, EmbeddedRecord
|
70
|
+
end
|
71
|
+
|
72
|
+
it "#embed_record defines <name>, <name>_id, <name>_id= methods" do
|
73
|
+
@cls.embed_record :color, :class => Color
|
74
|
+
@cls.method_defined?(:color).must_equal true
|
75
|
+
@cls.method_defined?(:color_id).must_equal true
|
76
|
+
@cls.method_defined?(:color_id=).must_equal true
|
77
|
+
end
|
78
|
+
|
79
|
+
it "#embed_records defines <name>, <name>_ids, <name>_ids= methods" do
|
80
|
+
@cls.embed_records :colors, :class => Color, :singular => :color
|
81
|
+
@cls.method_defined?(:colors).must_equal true
|
82
|
+
@cls.method_defined?(:color_ids).must_equal true
|
83
|
+
@cls.method_defined?(:color_ids=).must_equal true
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
class Car
|
88
|
+
include EmbeddedRecord
|
89
|
+
embed_record :color, :class => Color
|
90
|
+
attr_accessor :color_mask
|
91
|
+
end
|
92
|
+
|
93
|
+
describe "Object embedding record" do
|
94
|
+
before do
|
95
|
+
@car = Car.new
|
96
|
+
end
|
97
|
+
|
98
|
+
it "#<name>_id= sets the index of a record" do
|
99
|
+
@car.color_id = :green
|
100
|
+
@car.color_mask.must_equal Color.find(:green).index
|
101
|
+
end
|
102
|
+
|
103
|
+
it "#<name>_id returns id of a record" do
|
104
|
+
@car.color_mask = Color.find(:blue).index
|
105
|
+
@car.color_id.must_equal :blue
|
106
|
+
end
|
107
|
+
|
108
|
+
it "#<name> returns record" do
|
109
|
+
@car.color_mask = Color.find(:blue).index
|
110
|
+
@car.color.must_equal Color.find(:blue)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
class Shirt
|
115
|
+
include EmbeddedRecord
|
116
|
+
embed_records :colors, :class => Color, :singular => "color"
|
117
|
+
attr_accessor :colors_mask
|
118
|
+
end
|
119
|
+
|
120
|
+
describe "Object embedding records" do
|
121
|
+
before do
|
122
|
+
@shirt = Shirt.new
|
123
|
+
end
|
124
|
+
|
125
|
+
it "#<name>_ids= sets the indices of records" do
|
126
|
+
@shirt.color_ids = [:red, :blue]
|
127
|
+
@shirt.colors_mask.must_equal(2**0 + 2**2)
|
128
|
+
end
|
129
|
+
|
130
|
+
it "#<name>_ids returns ids of records" do
|
131
|
+
@shirt.colors_mask = 2**1 + 2**2
|
132
|
+
@shirt.color_ids = [:green, :blue]
|
133
|
+
end
|
134
|
+
|
135
|
+
it "#<name> returns records" do
|
136
|
+
@shirt.color_ids = [:green, :blue]
|
137
|
+
@shirt.colors.must_equal [Color.find(:green), Color.find(:blue)]
|
138
|
+
end
|
139
|
+
end
|
metadata
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: embedded_record
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Wojciech Mach
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-11-06 00:00:00.000000000Z
|
13
|
+
dependencies: []
|
14
|
+
description:
|
15
|
+
email:
|
16
|
+
- wojtek@wojtekmach.pl
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- .gitignore
|
22
|
+
- Gemfile
|
23
|
+
- README.md
|
24
|
+
- Rakefile
|
25
|
+
- embedded_record.gemspec
|
26
|
+
- lib/embedded_record.rb
|
27
|
+
- test/embedded_record_test.rb
|
28
|
+
homepage: ''
|
29
|
+
licenses: []
|
30
|
+
post_install_message:
|
31
|
+
rdoc_options: []
|
32
|
+
require_paths:
|
33
|
+
- lib
|
34
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
35
|
+
none: false
|
36
|
+
requirements:
|
37
|
+
- - ! '>='
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
requirements: []
|
47
|
+
rubyforge_project: embedded_record
|
48
|
+
rubygems_version: 1.8.10
|
49
|
+
signing_key:
|
50
|
+
specification_version: 3
|
51
|
+
summary: Embed objects in a bitmask field. Similar to bitmask-attribute and friends
|
52
|
+
test_files:
|
53
|
+
- test/embedded_record_test.rb
|