Giter Site home page Giter Site logo

classmeta's Introduction

Classmeta for Rails 3.x+

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.

Lets say you wanted whitelisted attributes to be different depending which class was including it as an association.

In car, you only want the names of passengers:

has_many :passengers, class_name: Person.meta(attrs: [:name]).name

In train, you want their seat preference:

has_many :riders, class_name: Person.meta(attrs: [:name, :seat_preference]).name

So, include the gem in Gemfile and bundle install:

gem 'classmeta'

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:

class DefaultTransformer
  def transform(klass, derived_from_class, options)
    status_column = options[:status_column] || 'record_status'
    klass.class_eval "self.table_name = #{derived_from_class.table_name.inspect}"
    klass.class_eval "self.primary_key = #{derived_from_class.primary_key.inspect}"
    klass.class_eval "self._accessible_attributes[:default] = #{options[:attrs].inspect}" if options[:attrs]
  end
end

And add this to environment.rb:

Classmeta::Options.configure({
  :transformers => {
    :default => DefaultTransformer
  }
})

Now magically car passengers have their accessible attributes set to :name and train riders to [:name, :seat_preference]!

How?

Hooking into Rails' load_missing_constant, duping the original class, and doing stuff to it in the transform.

Works with class reloading or caching.

Why?

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.

Unnamed metas

This generates a new class named "MyModel" 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:

MyModel.meta

This generates a new class named "MyModel" sending that class and options hash attrs: [:name, :status], another_key: 'another_value' 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:

MyModel.meta(attrs: [:name, :status], another_key: 'another_value')

This generates a new class named "MyModel" sending that class and options hash attrs: [:name, :status], another_key: 'another_value' to the transform function of transformer associated with the :awesome key in Classmeta::Options:

MyModel.meta(:awesome, attrs: [:name, :status], another_key: 'another_value')

This generates a new class named "MyModel" sending that class and options hash attrs: [:name, :status], another_key: 'another_value', and_a_hash: {a: 1, b: {c: 1, d: 2}} to the transform function of transformers associated with the :awesome and :bitchin keys in Classmeta::Options:

MyModel.meta(:awesome, :bitchin, attrs: [:name, :status], another_key: 'another_value', and_a_hash: {a: 1, b: {c: 1, d: 2}})

Named metas

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:

MyModel.named_meta('Fabular')

This generates a new class named "Fabular" sending that class and options hash attrs: [:name, :status], another_key: 'another_value' 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:

MyModel.named_meta('Fabular', {attrs: [:name, :status], another_key: 'another_value'})

This generates a new class named "Fabular" sending that class and options hash attrs: [:name, :status], another_key: 'another_value' to the transform function of transformer associated with the :awesome key in Classmeta::Options:

MyModel.named_meta('Fabular', :awesome, {attrs: [:name, :status], another_key: 'another_value'})

This generates a new class named "Fabular" sending that class and options hash attrs: [:name, :status], another_key: 'another_value', and_a_hash: {a: 1, b: {c: 1, d: 2}} to the transform function of transformers associated with the :awesome and :bitchin keys in Classmeta::Options:

MyModel.named_meta('Fabular', [:awesome, :bitchin], {attrs: [:name, :status], another_key: 'another_value', and_a_hash: {a: 1, b: {c: 1, d: 2}}})

'm' and 'n' functions

Instead of meta, use m:

MyModel.m(:awesome, :bitchin, attrs: [:name, :status], another_key: 'another_value', and_a_hash: {a: 1, b: {c: 1, d: 2}})

Instead of named_meta, use n:

MyModel.n('Fabular', [:awesome, :bitchin], {attrs: [:name, :status], another_key: 'another_value', and_a_hash: {a: 1, b: {c: 1, d: 2}}})

Try it out

Add to your Gemfile:

gem 'classmeta'

Install it:

bundle install

Open console:

rails c

Try it:

YourModel.meta(:echo)
YourModel.meta(:echo).name
YourModel.meta(:echo, {:say => 'Hello World!'})
YourModel.named_meta('Fabular', :echo, {:say => 'Hello World!'})
Fabular.all

Note: if you define Classmeta::Options.configure({...}), it automatically gets rid of the echo transformer which is just for demonstration purposes. If you want to use it at runtime, you can use:

Classmeta::Options.configure({
  :transformers => {
    :echo => Classmeta::Echo
  }
})

Just Rails?

Feel free to add off-Rails support and do a pull request.

Compatibility Notes

Not tested with Rails 2.3. Let me know if you have any compatibility issues.

Ruby 1.9 is expected because of the way SecureRandom is abused for classes that aren't given a defined name via named_meta.

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:

class SecureRandom
  # not guaranteed to be unique
  def self.uuid
    # from http://stackoverflow.com/questions/88311/how-best-to-generate-a-random-string-in-ruby
    o = [('a'..'z'),('A'..'Z')].map{|i|i.to_a}.flatten
    (0..50).map{o[rand(o.length)]}.join
  end
end

or maybe:

class SecureRandom
  def self.uuid
    ActiveSupport::SecureRandom.uuid
  end
end

Troubleshooting

Debugging

Add :debug => true to the Classmeta configuration:

Classmeta::Options.configure({
  :debug => true,
  ...
})

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.

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.

uninitialized constant (something) (NameError), etc.

Since Classmeta delegates to Rails's dependency loading, any errors you get like this:

.../classmeta/lib/classmeta/dependencies.rb:7:in `load_missing_constant': uninitialized constant (something) (NameError)

Just means Rails couldn't find your class.

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.

Updating from prior versions

  • 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)

License

Copyright (c) 2012 Gary S. Weaver, released under the MIT license.

classmeta's People

Contributors

garysweaver avatar

Stargazers

 avatar  avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.