github
Advanced Search
  • Home
  • Pricing and Signup
  • Explore GitHub
  • Blog
  • Login

mumboe / has_easy forked from swalterd/has_easy

  • Admin
  • Watch Unwatch
  • Fork
  • Your Fork
  • Pull Request
  • Download Source
    • 1
    • 2
  • Source
  • Commits
  • Network (2)
  • Downloads (0)
  • Wiki (1)
  • Graphs
  • Branch: master

click here to add a description

click here to add a homepage

  • Branches (1)
    • master ✓
  • Tags (0)
Sending Request…
Enable Donations

Pledgie Donations

Once activated, we'll place the following badge in your repository's detail box:
Pledgie_example
This service is courtesy of Pledgie.

Easy access and creation of "has many" relationships for ActiveRecord models. Use this plugin to add preferences, options, flags, etc to your models. — Read more

  cancel

  cancel
  • Private
  • Read-Only
  • HTTP Read-Only

This URL has Read+Write access

Change to added validation errors to base instead of value for errors 
returned from validation function. 
swalterd (author)
Wed Dec 31 12:52:52 -0800 2008
commit  edf4a638119218b225e049e9022205ef9df25562
tree    0b8a08f18a07bbe849e0110419bbc7d2b1e685c3
parent  0798b037f3d7d1eeeb0a05cc4765a79a0a65fe0e
has_easy /
name age
history
message
file CHANGELOG Loading commit data...
file MIT-LICENSE
file README
file README.rdoc
file Rakefile Tue Jun 17 14:27:03 -0700 2008 initial commit [Christopher J. Bottaro]
directory generators/ Tue Jun 17 14:27:03 -0700 2008 initial commit [Christopher J. Bottaro]
file init.rb
file install.rb
directory lib/
directory tasks/
directory test/
file uninstall.rb
README.rdoc

Easy access and creation of "has many" relationships.

What’s the difference between flags, preferences and options? Nothing really, they are just "has many" relationships. So why should I install a separate plugin for each one? This plugin can be used to add preferences, flags, options, etc to any model.

Installation

 git clone git://github.com/cjbottaro/has_easy.git vendor/plugins/has_easy
 script/generate has_easy_migration create_has_easy_things
 rake db:migrate
 rake db:test:prepare
 cd vendor/plugins/has_easy
 rake test

Example

 class User < ActiveRecord::Base
   has_easy :preferences do |p|
     p.define :color
     p.define :theme
   end
   has_easy :flags do |f|
     f.define :is_admin
     f.define :is_spammer
   end
 end

 user = User.new

 # hash like access
 user.preferences[:color] = 'red'
 user.preferences[:color] # => 'red'

 # object like access
 user.preferences.theme? # => false, shorthand for !!user.preferences.theme
 user.preferences.theme = "savage thunder"
 user.preferences.theme # => "savage thunder"
 user.preferences.theme? # => true

 # easy access for form inputs
 user.flags_is_admin? # => false, shorthand for !!user.flags_is_admin
 user.flags_is_admin = true
 user.flags_is_admin # => true
 user.flags_is_admin? # => true

 # save user's preferences
 user.preferences.save # will trickle down validation errors to user
 user.errors.empty? # hopefully true

 # save user's flags
 user.flags.save! # will raise exception on validation errors

Advanced Usage

There are a lot of options that you can use with has_easy:

  • aliasing
  • default values
  • inheriting default values from parent associations
  • calculated default values
  • type checking values
  • validating values
  • preprocessing values

In this section, we’ll go over how to use each option and explain why it’s useful.

:alias and :aliases

These options go on the has_easy method call and specify alternate ways of invoking the association.

  class User < ActiveRecord::Base
    has_easy :preferences, :aliases => [:prefs, :options] do |p|
      p.define :likes_cheese
    end
    has_easy :flags, :alias => :status do |p|
      p.define :is_admin
    end
  end

  user.preferences.likes_cheese = 'yes'
  user.prefs.likes_cheese => 'yes'
  user.options_likes_cheese => 'yes'
  user.prefs[:likes_cheese] => 'yes'
  user.options.likes_cheese? => true
  ...etc...

:default

Very simple. It does what you think it does.

 class User < ActiveRecord::Base
   has_easy :options do |p|
     p.define :gender, :default => 'female'
   end
 end

 User.new.options.gender # => 'female'

:default_through

Allows the model to inherit it’s default value from an association.

 class Client < ActiveRecord::Base
   has_many :users
   has_easy :options do |p|
     p.define :gender, :default => 'male'
   end
 end
 class User < ActiveRecord::Base
   belongs_to :client
   has_easy :options do |p|
     p.define :gender, :default_through => :client, :default => 'female'
   end
 end

 client = Client.create
 user = client.users.create
 user.options.gender # => 'male'

 client.options.gender = 'asexual'
 client.options.save
 user.client(true) # reload association
 user.options.gender # => 'asexual'

 User.new.options.gender => 'female'

:default_dynamic

Allows for calculated default values.

  class User < ActiveRecord::Base
    has_easy 'prefs' do |t|
      t.define :likes_cheese, :default_dynamic => :defaults_to_like_cheese
      t.define :is_dumb, :default_dynamic => Proc.new{ |user| user.dumb_post_count > 10 }
    end

    def defaults_to_like_cheese
      cheesy_post_count > 10
    end
  end

  user = User.new :cheesy_post_count => 5
  user.prefs.likes_cheese? => false

  user = User.new :cheesy_post_count => 11
  user.prefs.likes_cheese? => true

  user = User.new :dumb_post_count => 5
  user.prefs.is_dumb? => false

  user = User.new :dumb_post_count => 11
  user.prefs.is_dumb? => true

:type_check

Allows type checking of values (for people who are into that).

  class User < ActiveRecord::Base
    has_easy :prefs do |p|
      p.define :theme, :type_check => String
      p.define :dollars, :type_check => [Fixnum, Bignum]
    end
  end

  user.prefs.theme = 123
  user.prefs.save! # ActiveRecord::InvalidRecord exception raised with message like:
                   # 'theme' for has_easy('prefs') failed type check

  user.prefs.dollars = "hello world"
  user.prefs.save
  user.errors.empty? # => false
  user.errors.on(:prefs) # => 'dollars' for has_easy('prefs') failed type check

:validate

Make sure that values fit some kind of criteria. If you use a Proc or name a method with a Symbol to do validation, there are three ways to specify failure:

  1. return false
  2. raise a HasEasy::ValidationError
  3. return an array of custom validation error messages
  class User < ActiveRecord::Base
    has_easy :prefs do |p|
      p.define :foreground, :validate => ['red', 'blue', 'green']
      p.define :background, :validate => Proc.new{ |value| %w[black white grey].include?(value) }
      p.define :midground,  :validate => :midground_validator
    end
    def midground_validator(value)
      return ["msg1", msg2] unless %w[yellow brown purple].include?(value)
    end
  end

  user.prefs.foreground = 'yellow'
  user.prefs.save! # ActiveRecord::InvalidRecord exception raised with message like:
                   # 'theme' for has_easy('prefs') failed validation

  user.prefs.background = "pink"
  user.prefs.save
  user.errors.empty? => false
  user.errors.on(:prefs) => 'background' for has_easy('prefs') failed validation

  user.prefs.midground = "black"
  user.prefs.save
  user.errors.on(:prefs)[0] => "msg1"
  user.errors.on(:prefs)[1] => "msg2"

:preprocess

Alter the value before it goes through type checking and/or validation. This is useful when working with forms and boolean values. CAREFUL!! This option only applies to the underscore accessors, i.e. prefs_likes_cheese=, not prefs.likes_cheese= or prefs[:likes_cheese]=.

  class User < ActiveRecord::Base
    has_easy :prefs do |p|
      p.define :likes_cheese, :validate => [true, false],
                              :preprocess => Proc.new{ |value| ['true', 'yes'].include?(value) ? true : false }
    end
  end

  user.prefs.likes_cheese = 'yes' # :preprocess NOT invoked; it only applies to underscore accessors!!
  user.prefs.likes_cheese
  => 'yes'
  user.prefs.save! # exception, validation failed

  user.prefs_likes_cheese = 'yes' # :preprocess invoked
  user.prefs.likes_cheese
  => true
  user.prefs.save! # no exception

:postprocess

Alter the value when it is read. This is useful when working with forms and boolean values. CAREFUL!! This option only applies to the underscore accessors, i.e. prefs_likes_cheese, not prefs.likes_cheese or prefs[:likes_cheese].

  class User < ActiveRecord::Base
    has_easy :prefs do |p|
      p.define :likes_cheese, :validate => [true, false],
                              :postprocess => Proc.new{ |value| value ? 'yes' : 'no' }
    end
  end

  user.prefs.likes_cheese = true
  user.prefs.likes_cheese # :postprocess NOT invoked, it only applies to underscore accessors
  => true
  user.prefs_likes_cheese # :postprocess invoked
  => 'yes'

Using with Forms

Suppose you have a has_easy field defined as a boolean and you want to use it with a checkbox in form_for.

  (model)

  class User < ActiveRecord::Base
    has_easy :prefs do |p|
      p.define :likes_cheese, :type_check => [TrueClass, FalseClass],
                              :preprocess => Proc.new{ |value| value == 'yes' },
                              :postprocess => Proc.new{ |value| value ? 'yes' : 'no' }
    end
  end

  (view)

  <% form_for(@user) do |f| %>
    <%= f.check_box 'user', 'prefs_likes_cheese', {}, 'yes', 'no' %> # invokes @user.prefs_likes_cheese which does the :postprocess
  <% end %>

  (controller)

  @user.update_attributes(params[:user]) # invokes @user.prefs_likes_cheese= which does the :preprocess
  @user.prefs.save
  @user.prefs.likes_cheese
  => true or false
  @user.prefs_likes_cheese # remember, only underscore accessors invoke the :preprocess and :postprocess options
  => 'yes' or 'no'

The general idea is that we make the form use prefs_likes_cheese= and prefs_likes_cheese accessors which in turn use the :preprocess and :postprocess options. Then in our normal code, we use prefs.likes_cheese or prefs[:likes_cheese] accessors to get our expected boolean values.

Missing Features

Autovivification

For when we want to use fields without having to define them first.

  class User < ActiveRecord::Base
    has_easy :prefs, :autovivify => true do |p|
      p.define :likes_cheese, :default => 'yes'
    end
  end

  user.prefs.likes_cheese => 'yes'
  user.prefs.likes_pizza => nil
  user.prefs.likes_pizza = true
  user.prefs.likes_pizza => true

Scoping to other models

Ehh, can’t think of a way to describe this other than example. Also, the syntax is completely up in the air, there are so many different ways to do it, I have no idea which way to go with. Please tell me your ideas.

  class User < ActiveRecord::Base
    has_easy :prefs do |p|
      p.define :subscribed, :scoped => Post
      p.define :color, :scoped => [Car, Motorcycle] # polymorphic but must be Car or Motorcycle
      p.define :hair_color, :scoped => true # polymorphic no restrictions
      p.define :likes_cheese, :scoped => [Food, NilClass] # scoped and not scoped at the same time
    end
  end

  post = Post.find :first, :conditions => {:topic => 'rails'}
  me.prefs.subscribed? :to => post
  => true

  vette = Car.find :first, :conditions => {:model => 'corvette'}
  me.prefs.color :for => vette
  => 'black'

  gf = Girl.find :first, :conditions => {:name => 'aimee'}
  me.prefs.hair_color :on => gf
  => 'brown'

  watermelon = Food.find :first, :conditions => {:kind => 'watermelon'}
  my.prefs.likes_cheese? # not scoped; do I like cheese in general?
  => true
  my.prefs.likes_cheese? :on => watermelon # scoped; do I like cheese on watermelon?
  => false

Copyright © 2008 Christopher J. Bottaro <cjbottaro@alumni.cs.utexas.edu>, released under the MIT license

Blog | Support | Training | Contact | API | Status | Twitter | Help | Security
© 2010 GitHub Inc. All rights reserved. | Terms of Service | Privacy Policy
Powered by the Dedicated Servers and
Cloud Computing of Rackspace Hosting®
Dedicated Server