schemaker 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +11 -0
- data/LICENSE.txt +20 -0
- data/README.textile +49 -0
- data/Rakefile +49 -0
- data/VERSION +1 -0
- data/lib/schemaker/models/base_model.rb +204 -0
- data/lib/schemaker/models/join_model.rb +40 -0
- data/lib/schemaker/models/object_model.rb +33 -0
- data/lib/schemaker/models/subject_model.rb +41 -0
- data/lib/schemaker/models.rb +109 -0
- data/lib/schemaker.rb +14 -0
- data/schemaker.gemspec +71 -0
- data/spec/models_helper.rb +53 -0
- data/spec/schemaker/model/join_spec.rb +27 -0
- data/spec/schemaker/model/object_spec.rb +24 -0
- data/spec/schemaker/model/subject_spec.rb +25 -0
- data/spec/schemaker/models_spec.rb +16 -0
- data/spec/spec_helper.rb +6 -0
- metadata +126 -0
data/.document
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Kristian Mandrup
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.textile
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
h1. Schemaker
|
2
|
+
|
3
|
+
This is the *Schema Maker* also known as the *schemaker*. This gem can help configure your model relationships for 3 models, the subject, the join and the object model.
|
4
|
+
Typically when you add some behavior field to a subject (fx a UserAccount model class), this may require a 1-M or M-M relationship to the object of that relationship.
|
5
|
+
With _ActiveRecord_ this can require a join model or just a symmetrical _:has_and_belongs_to_many_. If you apply Schemaker, it will handle all this magic for you!
|
6
|
+
|
7
|
+
Schemaker is intended for use when creating gems that require somewhat complex model relationships for adding behavior. It does the "heavy lifting" on this part of the work.
|
8
|
+
There will likely be adapters for other data stores too, to simplify ensuring that your gem can support and store the relationships correctly for these data stores.
|
9
|
+
|
10
|
+
h2. Install
|
11
|
+
|
12
|
+
Gemfile:
|
13
|
+
|
14
|
+
@gem 'schemaker@
|
15
|
+
|
16
|
+
@$ bundle@
|
17
|
+
|
18
|
+
h2. Usage
|
19
|
+
|
20
|
+
Create a complex Many-to-Many join through the join model
|
21
|
+
|
22
|
+
<pre>require 'schemaker'
|
23
|
+
|
24
|
+
models = Schemaker::Models.new UserAccount, Role, UsersRoles, :subject_key => :troles
|
25
|
+
models.configure</pre>
|
26
|
+
|
27
|
+
Create a "quick join" (a symmetrical :has_and_belongs_to_many for the subject and object model)
|
28
|
+
|
29
|
+
@models.subject_model.quick_join@
|
30
|
+
|
31
|
+
Create a :has_one to relationship from the subject to the object
|
32
|
+
|
33
|
+
@models.subject_model.create_has_one :object@
|
34
|
+
|
35
|
+
h2. Contributing to schemaker
|
36
|
+
|
37
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
38
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
39
|
+
* Fork the project
|
40
|
+
* Start a feature/bugfix branch
|
41
|
+
* Commit and push until you are happy with your contribution
|
42
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
43
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
44
|
+
|
45
|
+
h2. Copyright
|
46
|
+
|
47
|
+
Copyright (c) 2011 Kristian Mandrup. See LICENSE.txt for
|
48
|
+
further details.
|
49
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "schemaker"
|
18
|
+
gem.homepage = "http://github.com/kristianmandrup/schemaker"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = %Q{Schema maker takes care of configuring your model schemas to map behavior}
|
21
|
+
gem.description = %Q{Configures relationships between subject, join and object model for a given behavior}
|
22
|
+
gem.email = "kmandrup@gmail.com"
|
23
|
+
gem.authors = ["Kristian Mandrup"]
|
24
|
+
# dependencies defined in Gemfile
|
25
|
+
end
|
26
|
+
Jeweler::RubygemsDotOrgTasks.new
|
27
|
+
|
28
|
+
require 'rspec/core'
|
29
|
+
require 'rspec/core/rake_task'
|
30
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
31
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
32
|
+
end
|
33
|
+
|
34
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
35
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
36
|
+
spec.rcov = true
|
37
|
+
end
|
38
|
+
|
39
|
+
task :default => :spec
|
40
|
+
|
41
|
+
require 'rake/rdoctask'
|
42
|
+
Rake::RDocTask.new do |rdoc|
|
43
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
44
|
+
|
45
|
+
rdoc.rdoc_dir = 'rdoc'
|
46
|
+
rdoc.title = "schemaker #{version}"
|
47
|
+
rdoc.rdoc_files.include('README*')
|
48
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
49
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
@@ -0,0 +1,204 @@
|
|
1
|
+
#
|
2
|
+
# @author Kristian Mandrup
|
3
|
+
#
|
4
|
+
# Basic Model configuration functionality
|
5
|
+
#
|
6
|
+
# Each type of model to be configured shares some common functionality and state, that is encapsulated here for reuse
|
7
|
+
#
|
8
|
+
module Schemaker
|
9
|
+
class BaseModel
|
10
|
+
attr_accessor :models, :my_class
|
11
|
+
attr_reader :logs # can be used to later check which relationships were set up
|
12
|
+
|
13
|
+
# @param [Schema::Models] each model needs to have access to the collection of models it may need to create relations with
|
14
|
+
# @param [Class] each model must have a reference to the Class it aims to configure!
|
15
|
+
def initialize models, my_class
|
16
|
+
raise ArgumentError, "The first argument must be a Schema::Models instance" if !models.is_a?(Schemaker::Models)
|
17
|
+
raise ArgumentError, "The second argument must be the Class that is to be configured" if !my_class.is_a?(Class)
|
18
|
+
|
19
|
+
@models = models
|
20
|
+
@my_class = my_class
|
21
|
+
@logs = []
|
22
|
+
end
|
23
|
+
|
24
|
+
# The models :subject and :object must both be configured
|
25
|
+
# with a has_many relationship to the join model which the has_many :through references
|
26
|
+
def configure
|
27
|
+
create_has_many :join, class_name_option(join_class)
|
28
|
+
end
|
29
|
+
|
30
|
+
# The class name of the Class to be configured
|
31
|
+
# @return [String] class name
|
32
|
+
def clazz_name
|
33
|
+
my_class.to_s
|
34
|
+
end
|
35
|
+
alias_method :my_class_name, :clazz_name
|
36
|
+
|
37
|
+
def self.model_types
|
38
|
+
[:object, :subject, :join]
|
39
|
+
end
|
40
|
+
|
41
|
+
# Generate convenience methods for: :object, :subject, :join
|
42
|
+
# - object_model
|
43
|
+
# - object_class
|
44
|
+
# - object_class_name
|
45
|
+
|
46
|
+
model_types.each do |model_type|
|
47
|
+
class_eval %{
|
48
|
+
def #{model_type}_model
|
49
|
+
models.#{model_type}_model
|
50
|
+
end
|
51
|
+
|
52
|
+
def #{model_type}_class
|
53
|
+
models.#{model_type}_class
|
54
|
+
end
|
55
|
+
|
56
|
+
def #{model_type}_class_name
|
57
|
+
#{model_type}_class.clazz_name
|
58
|
+
end
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
protected
|
63
|
+
|
64
|
+
# Is global logging turned on
|
65
|
+
def log_on?
|
66
|
+
Schemaker.log_on?
|
67
|
+
end
|
68
|
+
|
69
|
+
# Create a 'belongs_to' relationship on the model (Class)
|
70
|
+
# Example:
|
71
|
+
# Role.belongs_to :user, :class_name => 'User'
|
72
|
+
def create_belongs_to clazz, options = {}
|
73
|
+
make_relationship :belongs_to, clazz, options.merge(:key => singular_key(clazz))
|
74
|
+
end
|
75
|
+
|
76
|
+
# Create a 'has_many' relationship on the model (Class)
|
77
|
+
# Example:
|
78
|
+
# User.has_many :roles, :class_name => 'Role'
|
79
|
+
def create_has_many clazz, options = {}
|
80
|
+
make_relationship :has_many, clazz, options
|
81
|
+
end
|
82
|
+
|
83
|
+
# Create a 'has_many' relationship on the model (Class)
|
84
|
+
# Example:
|
85
|
+
# User.has_one :roles, :class_name => 'Role'
|
86
|
+
def create_has_one clazz, options = {}
|
87
|
+
make_relationship :has_one, clazz, options.merge(:key => singular_key(clazz))
|
88
|
+
end
|
89
|
+
|
90
|
+
# Create a 'has_many :through' relationship on the model (Class)
|
91
|
+
# Example:
|
92
|
+
# User.has_many :roles, :class_name => 'Role', :through => 'UsersRoles'
|
93
|
+
def create_has_many_through clazz, options = {}
|
94
|
+
create_has_many clazz, through_options(options)
|
95
|
+
end
|
96
|
+
|
97
|
+
# To setup symmetrical has_and_belongs_to_many relationship:
|
98
|
+
#
|
99
|
+
# Example:
|
100
|
+
#
|
101
|
+
# class UserAccount < ActiveRecord::Base
|
102
|
+
# has_and_belongs_to_many :troles, :class_name => 'Role'
|
103
|
+
# end
|
104
|
+
#
|
105
|
+
# class Role < ActiveRecord::Base
|
106
|
+
# has_and_belongs_to_many :user_accounts, :class_name => 'User'
|
107
|
+
# end
|
108
|
+
#
|
109
|
+
def has_and_belongs_many to_clazz, options = {}
|
110
|
+
make_relationship :has_and_belongs_to_many, to_clazz, options[:from]
|
111
|
+
to_clazz.make_relationship :has_and_belongs_to_many, my_class, options[:to]
|
112
|
+
end
|
113
|
+
|
114
|
+
# Creates a given type of relationship
|
115
|
+
# @param [Symbol] the type of relationship, fx :has_many
|
116
|
+
# @param [Class, Symbol] the Class that is the object of the relationship, fx Role for a User.has_many relationship
|
117
|
+
# @param [Hash] any extra relationship options, fx for a :through relationship, or to indicate :class_name etc.
|
118
|
+
def make_relationship relationship_name, clazz, options = {}
|
119
|
+
key_name = options.delete(:key) || key(clazz)
|
120
|
+
|
121
|
+
opts_str = options.empty? ? '' : options.inspect.insert(0, ', ').gsub(/[{}]/ , '')
|
122
|
+
log "#{my_class_name}.#{relationship_name} :#{key_name}#{opts_str}" if log_on?
|
123
|
+
|
124
|
+
return my_class.send(relationship_name, key_name) if options.empty?
|
125
|
+
|
126
|
+
my_class.send(relationship_name, key_name, options)
|
127
|
+
end
|
128
|
+
|
129
|
+
# creates a key for a given type
|
130
|
+
# @param type [Symbol] - either :object, :subject or :join
|
131
|
+
def key type
|
132
|
+
models.key type
|
133
|
+
end
|
134
|
+
|
135
|
+
def get_class type
|
136
|
+
models.get_class type
|
137
|
+
end
|
138
|
+
|
139
|
+
def singular_key cls_name
|
140
|
+
model = get_class(cls_name)
|
141
|
+
model.to_s.singularize.underscore
|
142
|
+
end
|
143
|
+
|
144
|
+
def make_key cls_name
|
145
|
+
models.make_key cls_name
|
146
|
+
end
|
147
|
+
|
148
|
+
# log the relationship being added
|
149
|
+
# - to STDOUT via puts
|
150
|
+
# - to a logs list
|
151
|
+
def log msg
|
152
|
+
puts msg
|
153
|
+
logs << msg
|
154
|
+
end
|
155
|
+
|
156
|
+
# sets up the :source relationship option, typically for a has_many through relationship
|
157
|
+
def source_option cls_name
|
158
|
+
model = get_class(cls_name)
|
159
|
+
{:source => source(model) }
|
160
|
+
end
|
161
|
+
|
162
|
+
# sets up the :class_name relationship option for a given class (model
|
163
|
+
# @param [Class, String] the class to point to
|
164
|
+
def class_name_option cls_name
|
165
|
+
model = get_class(cls_name)
|
166
|
+
{:class_name => model.to_s }
|
167
|
+
end
|
168
|
+
|
169
|
+
# sets up the :through relationship option, always points to the join model
|
170
|
+
def through_option
|
171
|
+
{:through => join_model.through_key }
|
172
|
+
end
|
173
|
+
|
174
|
+
# sets up the :foreign_key relationship option
|
175
|
+
# the foreign key name should always correspond to 'my own' class name
|
176
|
+
def foreign_key_option cls_name
|
177
|
+
model = get_class(cls_name)
|
178
|
+
{:foreign_key => foreign_key(model) }
|
179
|
+
end
|
180
|
+
|
181
|
+
# sets up the full :through relationship options
|
182
|
+
# Example:
|
183
|
+
# :class_name => 'Role', :through => 'UsersRoles', :source => :role, :foreign_key => :user_id)
|
184
|
+
def through_options cls_name
|
185
|
+
model = get_class(cls_name)
|
186
|
+
through_option.merge(source_option model).merge(class_name_option model)
|
187
|
+
end
|
188
|
+
|
189
|
+
# creates the source
|
190
|
+
# Role becomes :role
|
191
|
+
def source cls_name
|
192
|
+
model = get_class(cls_name)
|
193
|
+
model.to_s.underscore.singularize.to_sym
|
194
|
+
end
|
195
|
+
|
196
|
+
# creates the foreign key
|
197
|
+
# RefManyAccount becomes :account_id
|
198
|
+
def foreign_key cls_name
|
199
|
+
model = get_class(cls_name)
|
200
|
+
name = model.to_s.underscore.split('_').last.singularize
|
201
|
+
:"#{name}_id"
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
#
|
2
|
+
# @author Kristian Mandrup
|
3
|
+
#
|
4
|
+
# Model configuration of the Join model (fx UsersRoles)
|
5
|
+
#
|
6
|
+
# Knows how to configure the Join model between a subject model (the main target of the behavior fx UserAccount) and the object model (the behavior to be added, fx Role)
|
7
|
+
#
|
8
|
+
module Schemaker
|
9
|
+
class JoinModel < BaseModel
|
10
|
+
|
11
|
+
# @param [Schema::Models] each model needs to have access to the collection of models it may need to create relations with
|
12
|
+
# @param [Class] reference to the Class it aims to configure!
|
13
|
+
def initialize models, clazz
|
14
|
+
super
|
15
|
+
end
|
16
|
+
|
17
|
+
def simple_key
|
18
|
+
clazz_name.to_s.underscore
|
19
|
+
end
|
20
|
+
|
21
|
+
def through_key
|
22
|
+
make_key clazz_name
|
23
|
+
end
|
24
|
+
|
25
|
+
# The join model always belongs to both the object and subject model
|
26
|
+
# the subject and object model can then each have a has_many relationship to the join model
|
27
|
+
# thus creating a Many-to-Many relationship via the join model
|
28
|
+
|
29
|
+
# Example:
|
30
|
+
# UsersRoles
|
31
|
+
# belongs_to :user, :class_name => 'UserAccount' (subject)
|
32
|
+
# belongs_to :role, :class_name => 'Role' (object)
|
33
|
+
#
|
34
|
+
# @note Do not call super here!
|
35
|
+
def configure
|
36
|
+
create_belongs_to :subject, class_name_option(:subject)
|
37
|
+
create_belongs_to :object, class_name_option(:object)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
#
|
2
|
+
# @author Kristian Mandrup
|
3
|
+
#
|
4
|
+
# Model configuration of the Object model (fx Role)
|
5
|
+
#
|
6
|
+
# Knows how to configure the relationship fro the Object model to the subject model via the Join model
|
7
|
+
#
|
8
|
+
module Schemaker
|
9
|
+
class ObjectModel < BaseModel
|
10
|
+
|
11
|
+
# @param [Schema::Models] each model needs to have access to the collection of models it may need to create relations with
|
12
|
+
# @param [Class] reference to the Class it aims to configure!
|
13
|
+
def initialize models, clazz
|
14
|
+
super
|
15
|
+
end
|
16
|
+
|
17
|
+
# Configures has_many through relationship via Join model for the object model (fx Role)
|
18
|
+
#
|
19
|
+
# Example:
|
20
|
+
# Role (object)
|
21
|
+
# has_many :accounts, :class_name => 'RefManyAccount', :through => :accounts_roles (subject)
|
22
|
+
# has_many :user_roles, :class_name => 'UserRole' (join)
|
23
|
+
def configure
|
24
|
+
super
|
25
|
+
create_has_many_through :subject
|
26
|
+
end
|
27
|
+
|
28
|
+
# @note important to use super to avoid recursive stack overflow!
|
29
|
+
def through_options options = {}
|
30
|
+
options.merge super(:subject)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
#
|
2
|
+
# @author Kristian Mandrup
|
3
|
+
#
|
4
|
+
# Model configuration of the Subject model (fx Role)
|
5
|
+
#
|
6
|
+
# Knows how to configure the relationship fro the Object model to the subject model via the Join model
|
7
|
+
#
|
8
|
+
module Schemaker
|
9
|
+
class SubjectModel < BaseModel
|
10
|
+
|
11
|
+
# the main field for the behavior to add, fx :troles for adding roles behavior
|
12
|
+
attr_accessor :main_field
|
13
|
+
|
14
|
+
# @param [Schema::Models] each model needs to have access to the collection of models it may need to create relations with
|
15
|
+
# @param [Class] reference to the Class it aims to configure!
|
16
|
+
# @param [Symbol] the name of the main field for the behavior to add, fx :troles for adding roles behavior
|
17
|
+
def initialize models, clazz, main_field
|
18
|
+
super models, clazz
|
19
|
+
@main_field = main_field
|
20
|
+
end
|
21
|
+
|
22
|
+
# Used to set up a 'quick join' using Rails conventions and 'has_and_belongs_to_many' on the subject and object
|
23
|
+
def quick_join options = {}
|
24
|
+
create_has_and_belongs_to_many :object
|
25
|
+
end
|
26
|
+
|
27
|
+
# Example:
|
28
|
+
# UserAccount (subject)
|
29
|
+
# has_many :user_roles, :class_name => 'UserRole' (join)
|
30
|
+
# has_many :roles, :class_name => 'Role', :through => :users_roles (subject)
|
31
|
+
def configure
|
32
|
+
super
|
33
|
+
create_has_many_through :object, :key => main_field
|
34
|
+
end
|
35
|
+
|
36
|
+
# @note important to use super to avoid recursive stack overflow!
|
37
|
+
def through_options options = {}
|
38
|
+
options.merge super(:object).merge(foreign_key_option :subject)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
#
|
2
|
+
# @author Kristian Mandrup
|
3
|
+
#
|
4
|
+
# Model configuration
|
5
|
+
# Has responsibiloity to configure relationships between all models
|
6
|
+
# A single behavior decoration can have up to three acting models in a joined relationship
|
7
|
+
|
8
|
+
# The subject model:
|
9
|
+
# - is the model which is the subject of the behavior added.
|
10
|
+
# - it has the main field, which CAN be a relationship to the object model, either directly or via a join model (fx in Relational DBs)
|
11
|
+
#
|
12
|
+
# The object model:
|
13
|
+
# - is the object of interest to the subject, fx a Role model is the object of interest for a UserAccount (subject)
|
14
|
+
# - the object can be referenced directly, via a join model or embedded under the subject
|
15
|
+
#
|
16
|
+
# The join model:
|
17
|
+
# - is used to bind object to subject in a Many-to-Many relationship, typically for Relational DBs
|
18
|
+
# - it must have a foreign key for both object and subject model, and most often no primary key of its own
|
19
|
+
|
20
|
+
module Schemaker
|
21
|
+
class Models
|
22
|
+
attr_accessor :subject_model, :object_model, :join_model
|
23
|
+
|
24
|
+
# Sets up the models that take part in the model relationship to be configured
|
25
|
+
# @param subject_class [Class]
|
26
|
+
# @param object_class [Class]
|
27
|
+
# @param join_class [Class]
|
28
|
+
# @param options [Hash] - contains the key to be used for the main field (subject key) and possibly other options to configure the models more precisely as needed
|
29
|
+
def initialize subject_class, object_class, join_class, options = {}
|
30
|
+
@subject_model = SubjectModel.new self, subject_class, options[:subject_key]
|
31
|
+
@object_model = ObjectModel.new self, object_class
|
32
|
+
@join_model = JoinModel.new self, join_class
|
33
|
+
end
|
34
|
+
|
35
|
+
# creates a key for a given type
|
36
|
+
# @param type [Symbol] - either :object, :subject or :join
|
37
|
+
def key type
|
38
|
+
make_key get_class(type)
|
39
|
+
end
|
40
|
+
|
41
|
+
# configure each model in turn
|
42
|
+
def configure
|
43
|
+
[subject_model, object_model, join_model].each do
|
44
|
+
|model| model.configure
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def logs
|
49
|
+
@logs ||= [subject_model, object_model, join_model].inject([]) do |res, model|
|
50
|
+
res << model.logs
|
51
|
+
res
|
52
|
+
end.flatten
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.model_types
|
56
|
+
[:object, :subject, :join]
|
57
|
+
end
|
58
|
+
|
59
|
+
# generate methods:
|
60
|
+
# - object_class
|
61
|
+
# - subject_class
|
62
|
+
# - join_class
|
63
|
+
model_types.each do |model_type|
|
64
|
+
class_eval %{
|
65
|
+
def #{model_type}_class
|
66
|
+
#{model_type}_model.clazz_name
|
67
|
+
end
|
68
|
+
}
|
69
|
+
end
|
70
|
+
|
71
|
+
# retrieves a given Class ie. a type of model
|
72
|
+
# @param [Class, String, Symbol, BaseModel] which class to get
|
73
|
+
# @return [Class] the Class (model) of interest
|
74
|
+
def get_class type
|
75
|
+
case type
|
76
|
+
when Class
|
77
|
+
type
|
78
|
+
when BaseModel
|
79
|
+
type.my_class
|
80
|
+
when String, Symbol
|
81
|
+
return get_class send("#{type}_model") if [:subject, :object, :join].include?(type.to_sym)
|
82
|
+
type.to_s.constantize
|
83
|
+
else
|
84
|
+
raise "Can't determine a class from: #{type}"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# creates a key from a class name
|
89
|
+
# fx UsersRoles becomes :user_roles, where only the last part is pluralised!
|
90
|
+
# @param [String] the class name
|
91
|
+
def make_key class_name
|
92
|
+
name = class_name.to_s.pluralize.gsub(/::/, '__').underscore
|
93
|
+
only_last_part_plural(name).to_sym
|
94
|
+
end
|
95
|
+
|
96
|
+
protected
|
97
|
+
|
98
|
+
# Takes a composite name and makes a nice sounding key that follows the Rails conventions
|
99
|
+
# fx UsersRoles becomes :user_roles, where only the last part is pluralised!
|
100
|
+
# @param [String] the class name, fx UsersRoles
|
101
|
+
def only_last_part_plural cls_name
|
102
|
+
parts = cls_name.split('_')
|
103
|
+
name = parts.inject([]) do |res, part|
|
104
|
+
res << (part != parts.last ? part.singularize : part)
|
105
|
+
res
|
106
|
+
end.join('_')
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
data/lib/schemaker.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
module Schemaker
|
2
|
+
class << self
|
3
|
+
attr_accessor :log_on
|
4
|
+
|
5
|
+
alias_method :log_on?, :log_on
|
6
|
+
end
|
7
|
+
|
8
|
+
autoload :Models, 'schemaker/models'
|
9
|
+
|
10
|
+
autoload :BaseModel, 'schemaker/models/base_model'
|
11
|
+
autoload :JoinModel, 'schemaker/models/join_model'
|
12
|
+
autoload :ObjectModel, 'schemaker/models/object_model'
|
13
|
+
autoload :SubjectModel, 'schemaker/models/subject_model'
|
14
|
+
end
|
data/schemaker.gemspec
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{schemaker}
|
8
|
+
s.version = "0.1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = [%q{Kristian Mandrup}]
|
12
|
+
s.date = %q{2011-06-12}
|
13
|
+
s.description = %q{Configures relationships between subject, join and object model for a given behavior}
|
14
|
+
s.email = %q{kmandrup@gmail.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE.txt",
|
17
|
+
"README.textile"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".rspec",
|
22
|
+
"Gemfile",
|
23
|
+
"LICENSE.txt",
|
24
|
+
"README.textile",
|
25
|
+
"Rakefile",
|
26
|
+
"VERSION",
|
27
|
+
"lib/schemaker.rb",
|
28
|
+
"lib/schemaker/models.rb",
|
29
|
+
"lib/schemaker/models/base_model.rb",
|
30
|
+
"lib/schemaker/models/join_model.rb",
|
31
|
+
"lib/schemaker/models/object_model.rb",
|
32
|
+
"lib/schemaker/models/subject_model.rb",
|
33
|
+
"schemaker.gemspec",
|
34
|
+
"spec/models_helper.rb",
|
35
|
+
"spec/schemaker/model/join_spec.rb",
|
36
|
+
"spec/schemaker/model/object_spec.rb",
|
37
|
+
"spec/schemaker/model/subject_spec.rb",
|
38
|
+
"spec/schemaker/models_spec.rb",
|
39
|
+
"spec/spec_helper.rb"
|
40
|
+
]
|
41
|
+
s.homepage = %q{http://github.com/kristianmandrup/schemaker}
|
42
|
+
s.licenses = [%q{MIT}]
|
43
|
+
s.require_paths = [%q{lib}]
|
44
|
+
s.rubygems_version = %q{1.8.5}
|
45
|
+
s.summary = %q{Schema maker takes care of configuring your model schemas to map behavior}
|
46
|
+
|
47
|
+
if s.respond_to? :specification_version then
|
48
|
+
s.specification_version = 3
|
49
|
+
|
50
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
51
|
+
s.add_development_dependency(%q<activerecord>, [">= 3.0.1"])
|
52
|
+
s.add_development_dependency(%q<rspec>, [">= 2.6.0"])
|
53
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0.6"])
|
54
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.6.2"])
|
55
|
+
s.add_development_dependency(%q<rcov>, [">= 0"])
|
56
|
+
else
|
57
|
+
s.add_dependency(%q<activerecord>, [">= 3.0.1"])
|
58
|
+
s.add_dependency(%q<rspec>, [">= 2.6.0"])
|
59
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.6"])
|
60
|
+
s.add_dependency(%q<jeweler>, ["~> 1.6.2"])
|
61
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
62
|
+
end
|
63
|
+
else
|
64
|
+
s.add_dependency(%q<activerecord>, [">= 3.0.1"])
|
65
|
+
s.add_dependency(%q<rspec>, [">= 2.6.0"])
|
66
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.6"])
|
67
|
+
s.add_dependency(%q<jeweler>, ["~> 1.6.2"])
|
68
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'active_record'
|
3
|
+
require 'schemaker'
|
4
|
+
|
5
|
+
Schemaker.log_on = true
|
6
|
+
|
7
|
+
class UserAccount < ActiveRecord::Base
|
8
|
+
end
|
9
|
+
|
10
|
+
class Role < ActiveRecord::Base
|
11
|
+
end
|
12
|
+
|
13
|
+
class UsersRoles < ActiveRecord::Base
|
14
|
+
end
|
15
|
+
|
16
|
+
def join_class
|
17
|
+
UsersRoles
|
18
|
+
end
|
19
|
+
|
20
|
+
def object_class
|
21
|
+
Role
|
22
|
+
end
|
23
|
+
|
24
|
+
def subject_class
|
25
|
+
UserAccount
|
26
|
+
end
|
27
|
+
|
28
|
+
def logs_matches logs, str
|
29
|
+
logs.any? {|log| match? log, str }
|
30
|
+
end
|
31
|
+
|
32
|
+
def matches log, str
|
33
|
+
log.should match(str) # /#{Regexp.escape(str)}/
|
34
|
+
end
|
35
|
+
|
36
|
+
def match? log, str
|
37
|
+
log =~ /#{Regexp.escape(str)}/
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
def matches_all log, *strings
|
42
|
+
strings.each {|str| match? log, str}
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
def models_class
|
47
|
+
Schemaker::Models
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
def models
|
52
|
+
@models ||= models_class.new subject_class, object_class, join_class, :subject_key => :troles
|
53
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'models_helper'
|
2
|
+
|
3
|
+
class UsersRoles < ActiveRecord::Base
|
4
|
+
end
|
5
|
+
|
6
|
+
def join_model_class
|
7
|
+
Schemaker::JoinModel
|
8
|
+
end
|
9
|
+
|
10
|
+
describe join_model_class do
|
11
|
+
let(:join_model) do
|
12
|
+
join_model_class.new models, UsersRoles
|
13
|
+
end
|
14
|
+
|
15
|
+
describe '#configure' do
|
16
|
+
it 'should configure join model' do
|
17
|
+
join_model.configure
|
18
|
+
|
19
|
+
# check the logs!
|
20
|
+
last_log = join_model.logs.last
|
21
|
+
first_log = join_model.logs.first
|
22
|
+
|
23
|
+
matches_all first_log, 'belongs_to :user_account', ':class_name=>"UserAccount"'
|
24
|
+
matches_all last_log, 'belongs_to :role', ':class_name=>"Role"'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'models_helper'
|
2
|
+
|
3
|
+
def object_model_class
|
4
|
+
Schemaker::ObjectModel
|
5
|
+
end
|
6
|
+
|
7
|
+
describe object_model_class do
|
8
|
+
let(:object_model) do
|
9
|
+
object_model_class.new models, Role
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '#configure' do
|
13
|
+
it 'should configure object model' do
|
14
|
+
object_model.configure
|
15
|
+
|
16
|
+
# check the logs!
|
17
|
+
last_log = object_model.logs.last
|
18
|
+
first_log = object_model.logs.first
|
19
|
+
|
20
|
+
matches_all first_log, 'has_many :user_roles', ':class_name=>"UsersRoles"'
|
21
|
+
matches_all last_log, 'Role.has_many :user_accounts', ':through=>:user_roles', ':source=>:user_account', ':class_name=>"UserAccount"'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'troles/common/config/schema/models_helper'
|
2
|
+
|
3
|
+
def subject_model_class
|
4
|
+
Schemaker::SubjectModel
|
5
|
+
end
|
6
|
+
|
7
|
+
describe subject_model_class do
|
8
|
+
let(:subject_model) do
|
9
|
+
subject_model_class.new models, UserAccount, :troles
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '#configure' do
|
13
|
+
it 'should configure subject model' do
|
14
|
+
subject_model.configure
|
15
|
+
|
16
|
+
# check the logs!
|
17
|
+
last_log = subject_model.logs.last
|
18
|
+
first_log = subject_model.logs.first
|
19
|
+
|
20
|
+
matches_all first_log, 'has_many :user_roles', ':class_name=>"UsersRoles"'
|
21
|
+
# has_many :roles, {:through=>:user_roles, :source=>:role, :class_name=>"Role", :foreign_key=>:account_id}
|
22
|
+
matches_all last_log, 'UserAccount.has_many :troles', ':through=>:user_roles', ':source=>:role', ':class_name=>"Role"', ':foreign_key=>:account_id'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'models_helper'
|
2
|
+
|
3
|
+
describe models_class do
|
4
|
+
let(:models) do
|
5
|
+
# subject, object, join
|
6
|
+
models_class.new UserAccount, Role, UsersRoles, :subject_key => :troles
|
7
|
+
end
|
8
|
+
|
9
|
+
describe '#configure' do
|
10
|
+
it 'should configure all models' do
|
11
|
+
models.configure
|
12
|
+
|
13
|
+
logs_matches(models.logs, 'UserAccount.has_many :troles').should
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: schemaker
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Kristian Mandrup
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-06-12 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activerecord
|
16
|
+
requirement: &2156091260 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 3.0.1
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *2156091260
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rspec
|
27
|
+
requirement: &2156086680 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 2.6.0
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *2156086680
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: bundler
|
38
|
+
requirement: &2156085240 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 1.0.6
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *2156085240
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: jeweler
|
49
|
+
requirement: &2156084180 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.6.2
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *2156084180
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: rcov
|
60
|
+
requirement: &2156081880 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *2156081880
|
69
|
+
description: Configures relationships between subject, join and object model for a
|
70
|
+
given behavior
|
71
|
+
email: kmandrup@gmail.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files:
|
75
|
+
- LICENSE.txt
|
76
|
+
- README.textile
|
77
|
+
files:
|
78
|
+
- .document
|
79
|
+
- .rspec
|
80
|
+
- Gemfile
|
81
|
+
- LICENSE.txt
|
82
|
+
- README.textile
|
83
|
+
- Rakefile
|
84
|
+
- VERSION
|
85
|
+
- lib/schemaker.rb
|
86
|
+
- lib/schemaker/models.rb
|
87
|
+
- lib/schemaker/models/base_model.rb
|
88
|
+
- lib/schemaker/models/join_model.rb
|
89
|
+
- lib/schemaker/models/object_model.rb
|
90
|
+
- lib/schemaker/models/subject_model.rb
|
91
|
+
- schemaker.gemspec
|
92
|
+
- spec/models_helper.rb
|
93
|
+
- spec/schemaker/model/join_spec.rb
|
94
|
+
- spec/schemaker/model/object_spec.rb
|
95
|
+
- spec/schemaker/model/subject_spec.rb
|
96
|
+
- spec/schemaker/models_spec.rb
|
97
|
+
- spec/spec_helper.rb
|
98
|
+
homepage: http://github.com/kristianmandrup/schemaker
|
99
|
+
licenses:
|
100
|
+
- MIT
|
101
|
+
post_install_message:
|
102
|
+
rdoc_options: []
|
103
|
+
require_paths:
|
104
|
+
- lib
|
105
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
106
|
+
none: false
|
107
|
+
requirements:
|
108
|
+
- - ! '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
segments:
|
112
|
+
- 0
|
113
|
+
hash: -374026981416273501
|
114
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
115
|
+
none: false
|
116
|
+
requirements:
|
117
|
+
- - ! '>='
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
version: '0'
|
120
|
+
requirements: []
|
121
|
+
rubyforge_project:
|
122
|
+
rubygems_version: 1.8.5
|
123
|
+
signing_key:
|
124
|
+
specification_version: 3
|
125
|
+
summary: Schema maker takes care of configuring your model schemas to map behavior
|
126
|
+
test_files: []
|