classmeta 0.0.2 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +106 -13
- data/lib/classmeta/class.rb +40 -15
- data/lib/classmeta/class_registry.rb +6 -3
- data/lib/classmeta/echo.rb +2 -2
- data/lib/classmeta/options.rb +5 -0
- data/lib/classmeta/version.rb +1 -1
- metadata +2 -2
data/README.md
CHANGED
@@ -1,27 +1,30 @@
|
|
1
|
-
Classmeta for Rails 3.
|
1
|
+
Classmeta for Rails 3.x+
|
2
2
|
=====
|
3
3
|
|
4
|
-
Creates new classes on the fly to
|
4
|
+
Creates new classes on the fly in Rails, optionally transform them, and have kick-ass, short association descriptions that double as class definitions. Open a ticket to add to the README if you find an inventive use that you think others would benefit from.
|
5
5
|
|
6
6
|
Lets say you wanted whitelisted attributes to be different depending which class was including it as an association.
|
7
7
|
|
8
8
|
In car, you only want the names of passengers:
|
9
9
|
|
10
|
-
has_many :passengers, class_name: Person.meta(
|
10
|
+
has_many :passengers, class_name: Person.meta(attrs: [:name]).name
|
11
11
|
|
12
12
|
In train, you want their seat preference:
|
13
13
|
|
14
|
-
has_many :riders, class_name: Person.meta(
|
14
|
+
has_many :riders, class_name: Person.meta(attrs: [:name, :seat_preference]).name
|
15
15
|
|
16
16
|
So, include the gem in Gemfile and bundle install:
|
17
17
|
|
18
18
|
gem 'classmeta'
|
19
19
|
|
20
|
-
Add a transformer somewhere in the load path
|
20
|
+
Add a transformer somewhere in the load path, e.g. to transform models, maybe you could put this in `app/models/default_transformer.rb` which would ensure the `table_name`, `primary_key` are set to be the same as the original class, and it would also allow the `_accessible_attributes` whitelist to be overriden with `:attrs` value in the options hash:
|
21
21
|
|
22
|
-
class
|
23
|
-
def transform(
|
24
|
-
|
22
|
+
class DefaultTransformer
|
23
|
+
def transform(klass, derived_from_class, options)
|
24
|
+
status_column = options[:status_column] || 'record_status'
|
25
|
+
klass.class_eval "self.table_name = #{derived_from_class.table_name.inspect}"
|
26
|
+
klass.class_eval "self.primary_key = #{derived_from_class.primary_key.inspect}"
|
27
|
+
klass.class_eval "self._accessible_attributes[:default] = #{options[:attrs].inspect}" if options[:attrs]
|
25
28
|
end
|
26
29
|
end
|
27
30
|
|
@@ -29,7 +32,7 @@ And add this to environment.rb:
|
|
29
32
|
|
30
33
|
Classmeta::Options.configure({
|
31
34
|
:transformers => {
|
32
|
-
:
|
35
|
+
:default => DefaultTransformer
|
33
36
|
}
|
34
37
|
})
|
35
38
|
|
@@ -45,11 +48,53 @@ Works with class reloading or caching.
|
|
45
48
|
|
46
49
|
It's useful if you have a number of classes, like models, that just differ by a little bit and module includes just aren't solving the problem of doubling, tripling, etc. the number of files you are having to create just to represent new classes.
|
47
50
|
|
48
|
-
###
|
51
|
+
### Unnamed metas
|
49
52
|
|
50
|
-
|
53
|
+
This generates a new class named "MyModel<some unique random string>" sending that class and nil options hash to the transform function of transformer associated with the :default key in Classmeta::Options if there is one, otherwise it will just generate a new model class with the same behavior as the old one and not transform it:
|
54
|
+
|
55
|
+
MyModel.meta
|
56
|
+
|
57
|
+
This generates a new class named "MyModel<some unique random string>" sending that class and options hash `attrs: [:name, :status], special_column: 'my_column'` to the transform function of transformer associated with the :default key in Classmeta::Options if there is one, otherwise it will just generate a new model class with the same behavior as the old one and not transform it:
|
58
|
+
|
59
|
+
MyModel.meta(attrs: [:name, :status], special_column: 'my_column')
|
60
|
+
|
61
|
+
This generates a new class named "MyModel<some unique random string>" sending that class and options hash `attrs: [:name, :status], special_column: 'my_column'` to the transform function of transformer associated with the :awesome key in Classmeta::Options:
|
62
|
+
|
63
|
+
MyModel.meta(:awesome, attrs: [:name, :status], special_column: 'my_column')
|
64
|
+
|
65
|
+
This generates a new class named "MyModel<some unique random string>" sending that class and options hash `attrs: [:name, :status], special_column: 'my_column', somethin_else: {a: 1, b: {c: 1, d: 2}}` to the transform function of transformers associated with the :awesome and :bitchin keys in Classmeta::Options:
|
66
|
+
|
67
|
+
MyModel.meta(:awesome, :bitchin, attrs: [:name, :status], special_column: 'my_column', somethin_else: {a: 1, b: {c: 1, d: 2}})
|
68
|
+
|
69
|
+
### Named metas
|
70
|
+
|
71
|
+
This generates a new class named "Fabular" sending that class and nil options hash to the transform function of transformer associated with the :default key in Classmeta::Options if there is one, otherwise it will just generate a new model class with the same behavior as the old one and not transform it:
|
72
|
+
|
73
|
+
MyModel.named_meta('Fabular')
|
51
74
|
|
52
|
-
|
75
|
+
This generates a new class named "Fabular" sending that class and options hash `attrs: [:name, :status], special_column: 'my_column'` to the transform function of transformer associated with the :default key in Classmeta::Options if there is one, otherwise it will just generate a new model class with the same behavior as the old one and not transform it:
|
76
|
+
|
77
|
+
MyModel.named_meta('Fabular', {attrs: [:name, :status], special_column: 'my_column'})
|
78
|
+
|
79
|
+
This generates a new class named "Fabular" sending that class and options hash `attrs: [:name, :status], special_column: 'my_column'` to the transform function of transformer associated with the :awesome key in Classmeta::Options:
|
80
|
+
|
81
|
+
MyModel.named_meta('Fabular', :awesome, {attrs: [:name, :status], special_column: 'my_column'})
|
82
|
+
|
83
|
+
This generates a new class named "Fabular" sending that class and options hash `attrs: [:name, :status], special_column: 'my_column', somethin_else: {a: 1, b: {c: 1, d: 2}}` to the transform function of transformers associated with the :awesome and :bitchin keys in Classmeta::Options:
|
84
|
+
|
85
|
+
MyModel.named_meta('Fabular', [:awesome, :bitchin], {attrs: [:name, :status], special_column: 'my_column', somethin_else: {a: 1, b: {c: 1, d: 2}}})
|
86
|
+
|
87
|
+
### 'm' and 'n' functions
|
88
|
+
|
89
|
+
Instead of `meta`, use `m`:
|
90
|
+
|
91
|
+
MyModel.m(:awesome, :bitchin, attrs: [:name, :status], special_column: 'my_column', somethin_else: {a: 1, b: {c: 1, d: 2}})
|
92
|
+
|
93
|
+
Instead of `named_meta`, use `n`:
|
94
|
+
|
95
|
+
MyModel.n('Fabular', [:awesome, :bitchin], {attrs: [:name, :status], special_column: 'my_column', somethin_else: {a: 1, b: {c: 1, d: 2}}})
|
96
|
+
|
97
|
+
### Try it out
|
53
98
|
|
54
99
|
Add to your Gemfile:
|
55
100
|
|
@@ -79,15 +124,63 @@ Note: if you define `Classmeta::Options.configure({...})`, it automatically gets
|
|
79
124
|
}
|
80
125
|
})
|
81
126
|
|
127
|
+
### Just Rails?
|
128
|
+
|
129
|
+
Feel free to add off-Rails support and do a pull request.
|
130
|
+
|
131
|
+
### Compatibility Notes
|
132
|
+
|
133
|
+
Not tested with Rails 2.3. Let me know if you have any compatibility issues.
|
134
|
+
|
135
|
+
Ruby 1.9 is expected because of the way `SecureRandom` is abused for classes that aren't given a defined name via `named_meta`.
|
136
|
+
|
137
|
+
But, it should work in Ruby 1.8.x if you try adding one of the following on the load path, e.g. in `config/environment.rb`:
|
138
|
+
|
139
|
+
class SecureRandom
|
140
|
+
# not guaranteed to be unique
|
141
|
+
def self.uuid
|
142
|
+
# from http://stackoverflow.com/questions/88311/how-best-to-generate-a-random-string-in-ruby
|
143
|
+
o = [('a'..'z'),('A'..'Z')].map{|i|i.to_a}.flatten
|
144
|
+
(0..50).map{o[rand(o.length)]}.join
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
or maybe:
|
149
|
+
|
150
|
+
class SecureRandom
|
151
|
+
def self.uuid
|
152
|
+
ActiveSupport::SecureRandom.uuid
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
82
156
|
### Troubleshooting
|
83
157
|
|
158
|
+
#### Debugging
|
159
|
+
|
160
|
+
Add `:debug => true` to the Classmeta configuration:
|
161
|
+
|
162
|
+
Classmeta::Options.configure({
|
163
|
+
:debug => true,
|
164
|
+
...
|
165
|
+
})
|
166
|
+
|
167
|
+
#### OriginalModelName.n('NewModelName') results in (Table doesn't exist) being displayed next to model instance, or get error: 'relation "new_model_names" does not exist', etc.
|
168
|
+
|
169
|
+
If you are duplicating the model class instance and renaming the new instance with classmeta, then it still has to deal with ActiveRecord as if you defined a new model class with that name, so if the original class name didn't define table name, etc. explicitly and ActiveRecord is trying to intuit those from the classname, it still won't find them.
|
170
|
+
|
171
|
+
#### uninitialized constant (something) (NameError), etc.
|
172
|
+
|
84
173
|
Since Classmeta delegates to Rails's dependency loading, any errors you get like this:
|
85
174
|
|
86
175
|
.../classmeta/lib/classmeta/dependencies.rb:7:in `load_missing_constant': uninitialized constant (something) (NameError)
|
87
176
|
|
88
177
|
Just means Rails couldn't find your class.
|
89
178
|
|
90
|
-
For other errors,
|
179
|
+
For these and other errors, pretend you are getting that from `.../activesupport/lib/activesupport/dependencies.rb`, then Google it and scratch your head until you figure out you mistyped something.
|
180
|
+
|
181
|
+
### Updating from prior versions
|
182
|
+
|
183
|
+
* v0.0.2 -> v0.1.0: breaking transform method signature change- now expects to be able to send in the derived from class: `def transform(klass, derived_from_class, options)`
|
91
184
|
|
92
185
|
### License
|
93
186
|
|
data/lib/classmeta/class.rb
CHANGED
@@ -2,33 +2,58 @@ require 'securerandom'
|
|
2
2
|
|
3
3
|
module Classmeta
|
4
4
|
module ClassMethods
|
5
|
+
# Generates a unique classname and calls named_meta with provided parameters.
|
5
6
|
# e.g. has_many :employees, class_name: MyModel.meta(:ref, attrs: [:name, :status], special_column: 'my_column').name
|
6
7
|
def meta(*args)
|
8
|
+
puts "#{self.name}.meta called with args=#{args.inspect}" if Classmeta::Options.debugging?
|
7
9
|
options = args.extract_options! # separates options hash and leaves one or more transform names
|
8
10
|
self.named_meta("#{self}#{SecureRandom.uuid.gsub('-','')}", args, options)
|
9
11
|
end
|
12
|
+
alias_method :m, :meta
|
10
13
|
|
11
|
-
#
|
12
|
-
# e.g. has_many :employees, class_name: MyModel.named_meta(MyNewModel,
|
13
|
-
|
14
|
-
|
15
|
-
#
|
14
|
+
# Define a new class with specified classname using the specified transformer(s) and options.
|
15
|
+
# e.g. has_many :employees, class_name: MyModel.named_meta(MyNewModel, :transformer_key, {attrs: [:name, :status], special_column: 'my_column'}).name
|
16
|
+
# e.g. has_many :employees, class_name: MyModel.named_meta(MyNewModel, [:transformer_key1, :transformer_key2], {attrs: [:name, :status], special_column: 'my_column'}).name
|
17
|
+
def named_meta(new_classname, transformer_key_or_keys = [], options = {})
|
18
|
+
# handle unspecified transformer_key_or_keys
|
19
|
+
if transformer_key_or_keys.is_a?(Hash) && options.is_a?(Hash) && options.size == 0
|
20
|
+
options = transformer_key_or_keys
|
21
|
+
transformer_key_or_keys = []
|
22
|
+
end
|
23
|
+
|
24
|
+
if Classmeta::Options.debugging?
|
25
|
+
puts "#{self.name}.named_meta called with new_classname=#{new_classname.inspect}, transformer_key_or_keys=#{transformer_key_or_keys.inspect}, options=#{options.inspect}"
|
26
|
+
puts "#{self.name} calling #{self.name}.dup" if Classmeta::Options.debugging?
|
27
|
+
end
|
16
28
|
new_class = self.dup
|
29
|
+
puts "#{self.name} defining function called 'name' on new class #{new_class} to return #{new_classname.inspect}" if Classmeta::Options.debugging?
|
17
30
|
new_class.instance_eval("def name; #{new_classname.inspect}; end")
|
18
|
-
|
19
|
-
|
31
|
+
transformer_keys = Array.wrap(transformer_key_or_keys).flatten
|
32
|
+
if transformer_keys && transformer_keys.size > 0
|
33
|
+
puts "#{self.name} using Transformers #{transformer_keys.inspect} to transform class #{new_classname.inspect}" if Classmeta::Options.debugging?
|
34
|
+
transformer_keys.each do |transformer_key|
|
35
|
+
transformer = Classmeta::Options.get_transformer(transformer)
|
36
|
+
if transformer
|
37
|
+
puts "#{self.name} calling #{transformer.name}.transform(#{new_class}, #{self}, #{options.inspect})" if Classmeta::Options.debugging?
|
38
|
+
transformer.transform(new_class, self, options)
|
39
|
+
else
|
40
|
+
Classmeta::Options.output if Classmeta::Options.debugging?
|
41
|
+
raise "Classmeta::Options needs a :transformers hash that contains a key called #{transformer_key.inspect}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
else
|
45
|
+
transformer = Classmeta::Options.get_transformer(:default)
|
46
|
+
puts "Classmeta::Options's :default transformer = #{transformer.inspect}" if Classmeta::Options.debugging?
|
20
47
|
if transformer
|
21
|
-
|
22
|
-
transformer.transform(new_class, options)
|
23
|
-
else
|
24
|
-
Classmeta::Options.output
|
25
|
-
raise "Classmeta::Options needs a transformer mapping for #{transform.inspect}"
|
48
|
+
puts "#{self.name} calling #{transformer.name}.transform(#{new_class}, #{self}, #{options.inspect})" if Classmeta::Options.debugging?
|
49
|
+
transformer.transform(new_class, self, options)
|
26
50
|
end
|
27
51
|
end
|
28
|
-
|
29
|
-
Classmeta::ClassRegistry.register(new_classname, [self,
|
30
|
-
# TODO:
|
52
|
+
puts "#{self.name} registering #{name} so class lookup works in Rails" if Classmeta::Options.debugging?
|
53
|
+
Classmeta::ClassRegistry.register(new_classname, [self, transformer_key_or_keys, options])
|
54
|
+
# TODO: use ActiveSupport::Dependencies::Registry.store(new_class) ?
|
31
55
|
new_class
|
32
56
|
end
|
57
|
+
alias_method :n, :named_meta
|
33
58
|
end
|
34
59
|
end
|
@@ -3,16 +3,19 @@ module Classmeta
|
|
3
3
|
@@registry = {}
|
4
4
|
|
5
5
|
def self.register(name, arr)
|
6
|
+
puts "Classmeta::ClassRegistry registering #{name.inspect}, #{arr.inspect}" if Classmeta::Options.debugging?
|
6
7
|
@@registry[name.to_sym] = arr
|
7
8
|
end
|
8
9
|
|
9
|
-
def self.get(name)
|
10
|
+
def self.get(name)
|
11
|
+
puts "Classmeta::ClassRegistry.get called" if Classmeta::Options.debugging?
|
10
12
|
arr = @@registry[name.to_sym]
|
11
13
|
if arr
|
12
14
|
# re-meta, so the class that was derived from can be reloaded
|
13
|
-
arr[0].
|
15
|
+
puts "Classmeta::ClassRegistry calling #{arr[0].name}.named_meta with #{name.inspect}, #{arr[1].inspect}, #{arr[2].inspect}" if Classmeta::Options.debugging?
|
16
|
+
arr[0].named_meta(name, arr[1], arr[2])
|
14
17
|
else
|
15
|
-
|
18
|
+
puts "Classmeta::ClassRegistry didn't find anything for name #{name} in Classmeta's class registry: #{@@registry.inspect}" if Classmeta::Options.debugging?
|
16
19
|
nil
|
17
20
|
end
|
18
21
|
end
|
data/lib/classmeta/echo.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module Classmeta
|
2
2
|
class Echo
|
3
|
-
def self.transform(
|
4
|
-
puts "Classmeta::Echo.transform(klazz, options) called with
|
3
|
+
def self.transform(klass, derived_from_class, options)
|
4
|
+
puts "Classmeta::Echo.transform(klazz, options) called with klass: #{klass.name}, derived_from_class: #{derived_from_class.name}, and options: #{options.inspect}"
|
5
5
|
end
|
6
6
|
end
|
7
7
|
end
|
data/lib/classmeta/options.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
module Classmeta
|
2
2
|
class Options
|
3
3
|
@@options = {
|
4
|
+
:debug => false,
|
4
5
|
:transformers => {
|
5
6
|
:echo => Classmeta::Echo
|
6
7
|
}
|
@@ -10,6 +11,10 @@ module Classmeta
|
|
10
11
|
@@options = hash
|
11
12
|
end
|
12
13
|
|
14
|
+
def self.debugging?
|
15
|
+
@@options[:debug]
|
16
|
+
end
|
17
|
+
|
13
18
|
def self.get_transformer(name)
|
14
19
|
(@@options[:transformers])[name]
|
15
20
|
end
|
data/lib/classmeta/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: classmeta
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-09-10 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: Magic class creator that lets you create and transform classes dynamically.
|
15
15
|
email:
|