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 CHANGED
@@ -1,27 +1,30 @@
1
- Classmeta for Rails 3.2+
1
+ Classmeta for Rails 3.x+
2
2
  =====
3
3
 
4
- Creates new classes on the fly to allow you to refactor common changes.
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(:std, attrs: [:name]).name
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(:std, attrs: [:name, :seat_preference]).name
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 like app/models/:
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 StandardTransformer
23
- def transform(klazz, options)
24
- klazz.class_eval "self._accessible_attributes[:default] = #{options[:attrs].inspect}" if options[:attrs]
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
- :std => StandardTransformer
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
- ### Just Rails?
51
+ ### Unnamed metas
49
52
 
50
- Feel free to add off-Rails support and do a pull request.
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
- ### Quick Test!
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, too, for the most part, 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.
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
 
@@ -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
- # e.g. has_many :employees, class_name: MyModel.named_meta(MyNewModel, :ref, {attrs: [:name, :status], special_column: 'my_column'}).name
12
- # e.g. has_many :employees, class_name: MyModel.named_meta(MyNewModel, [:transform1, :transform2], {attrs: [:name, :status], special_column: 'my_column'}).name
13
- def named_meta(new_classname, transform_name_or_names, options = {})
14
- transforms = Array.wrap(transform_name_or_names).flatten
15
- #puts "#{self.name}.dup will be named: #{name}"
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
- transforms.each do |transform|
19
- transformer = Classmeta::Options.get_transformer(transform)
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
- #puts "#{transformer.name}.transform(#{new_class.name}, #{options.inspect})"
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
- #puts "Registering #{name} so class lookup works, if riding Rails"
29
- Classmeta::ClassRegistry.register(new_classname, [self, transforms])
30
- # TODO: figure out how to cache class in Rails: ActiveSupport::Dependencies::Registry.store(new_class)
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].meta(arr[1])
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
- #puts "Didn't find anything for name #{name} in Classmeta's class registry: #{@@registry.inspect}"
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
@@ -1,7 +1,7 @@
1
1
  module Classmeta
2
2
  class Echo
3
- def self.transform(klazz, options)
4
- puts "Classmeta::Echo.transform(klazz, options) called with klazz: #{klazz.name} and options: #{options.inspect}"
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
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Classmeta
2
- VERSION = '0.0.2'
2
+ VERSION = '0.1.0'
3
3
  end
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.2
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-08-30 00:00:00.000000000 Z
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: