embedded_record 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ .*.sw*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in embedded_record.gemspec
4
+ gemspec
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,8 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new :test do |t|
5
+ t.pattern = "test/**/*_test.rb"
6
+ end
7
+
8
+ task :default => :test
@@ -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