has_magic_columns 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color --format documentation
data/README.md CHANGED
@@ -30,37 +30,37 @@ Usage
30
30
 
31
31
  Sprinkle a little magic into an existing model:
32
32
 
33
- class User < ActiveRecord::Base
33
+ class Person < ActiveRecord::Base
34
34
  has_magic_columns
35
35
  end
36
36
 
37
37
  Add magic columns to your model:
38
38
 
39
- @bob = User.create(:email => "bob@example.com")
40
- @bob.magic_columns.create(:name => "first_name")
39
+ @charlie = Person.create(:email => "charlie@example.com")
40
+ @charlie.magic_columns.create(:name => "first_name")
41
41
 
42
42
  Supply additional options if you have more specific requirements for your columns:
43
43
 
44
- @bob.magic_columns.create(:name => "last_name", :is_required => true)
45
- @bob.magic_columns.create(:name => "birthday", :datatype => :date)
46
- @bob.magic_columns.create(:name => "salary", :default => "40000", :pretty_name => "Yearly Salary")
44
+ @charlie.magic_columns.create(:name => "last_name", :is_required => true)
45
+ @charlie.magic_columns.create(:name => "birthday", :datatype => :date)
46
+ @charlie.magic_columns.create(:name => "salary", :default => "40000", :pretty_name => "Yearly Salary")
47
47
 
48
48
  The :datatype option supports :check_box_boolean, :date, :datetime, or :integer.
49
49
 
50
50
  Use your new columns just like you would with any other ActiveRecord attribute:
51
51
 
52
- @bob.first_name = "Bob"
53
- @bob.last_name = "Magic!"
54
- @bob.birthday = Date.today
55
- @bob.save
52
+ @charlie.first_name = "Charlie"
53
+ @charlie.last_name = "Magic!"
54
+ @charlie.birthday = Date.today
55
+ @charlie.save
56
56
 
57
- Find @bob and inspect him:
57
+ Find @charlie and inspect him:
58
58
 
59
- @bob = User.find(@bob.id)
60
- @bob.first_name #=> "Bob"
61
- @bob.last_name #=> "Magic!"
62
- @bob.birthday #=> #<Date: 4908497/2,0,2299161>
63
- @bob.salary #=> "40000", this is from :salary having a :default
59
+ @charlie = User.find(@charlie.id)
60
+ @charlie.first_name #=> "Charlie"
61
+ @charlie.last_name #=> "Magic!"
62
+ @charlie.birthday #=> #<Date: 4908497/2,0,2299161>
63
+ @charlie.salary #=> "40000", this is from :salary having a :default
64
64
 
65
65
  ## Inherited Model
66
66
 
@@ -71,6 +71,7 @@ as having magic columns:
71
71
  has_many :users
72
72
  has_magic_columns
73
73
  end
74
+ @account = Account.create(:name => "BobCorp")
74
75
 
75
76
  And declare the child as having magic columns :through the parent.
76
77
 
@@ -78,36 +79,34 @@ And declare the child as having magic columns :through the parent.
78
79
  belongs_to :account
79
80
  has_magic_columns :through => :account
80
81
  end
82
+ @alice = User.create(:name => "alice", :account => @account)
81
83
 
82
84
  To see all the magic columns available for a child from its parent:
83
85
 
84
- @user.magic_columns #=> [#<MagicColumn>,...]
85
- @user.account.magic_columns #=> [#<MagicColumn>,...]
86
+ @alice.magic_columns #=> [#<MagicColumn>,...]
87
+ @account.magic_columns #=> [#<MagicColumn>,...]
88
+ @alice.account.magic_columns #=> [#<MagicColumn>,...]
86
89
 
87
90
  To add magic columns, go through the parent or child:
88
91
 
89
- @user.magic_columns.create(...)
90
- @user.account.magic_columns.create(...)
92
+ @alice.magic_columns.create(...)
93
+ @account.magic_columns.create(...)
91
94
 
92
95
  All children for a given parent will have access to the same magic columns:
93
96
 
94
- @account = Account.create(:name => "BobCorp")
97
+ @alice.magic_columns.create(:name => "salary")
98
+ @alice.salary = "40000"
95
99
 
96
100
  @bob = User.create(:name => "bob", :account => @account)
97
- @bob.magic_columns.create(:name => "salary")
98
- @bob.salary = "40000"
99
-
100
- @steve = User.create(:name => "bob", :account => @account)
101
- # no need to add the column again
102
- @steve.salary = "50000"
101
+ # Magic! No need to add the column again!
102
+ @bob.salary = "50000"
103
103
 
104
104
  To Do
105
105
  =====
106
106
 
107
- This gem is mostly functional. Here's a short list of things that need to be
108
- done to polish it up:
107
+ Here's a short list of things that need to be done to polish up this gem:
109
108
 
110
- * Test
109
+ * Test other parts of the data model (e.g. magic_attributes, magic_options)
111
110
  * Benchmark and optimize
112
111
 
113
112
  Maintainers
@@ -119,3 +118,9 @@ Maintainers
119
118
  Contribute
120
119
  ==========
121
120
  See the [CONTRIBUTORS guide](https://github.com/latortuga/has_magic_columns/blob/master/CONTRIBUTORS.md).
121
+
122
+ Credits
123
+ =======
124
+
125
+ * Thank you to Brandon Keene for his original work making this plugin.
126
+ * Thank you to the [will_paginate](https://github.com/mislav/will_paginate) gem for iinspiration and code examples for how to test a Rails plugin.
data/Rakefile CHANGED
@@ -1,20 +1,11 @@
1
1
  require 'rake'
2
2
  gem 'rdoc'
3
3
  require 'rdoc/task'
4
- require 'rake/testtask'
4
+ require 'rspec/core/rake_task'
5
5
 
6
6
  require 'bundler'
7
7
  Bundler::GemHelper.install_tasks
8
8
 
9
- #desc 'Default: run unit tests.'
10
- task :default => :test
11
-
12
- Rake::TestTask.new do |t|
13
- t.libs << 'lib'
14
- t.pattern = 'test/**/*_test.rb'
15
- t.verbose = true
16
- end
17
-
18
9
  desc 'Generate documentation.'
19
10
  RDoc::Task.new do |rdoc|
20
11
  rdoc.main = "README.md"
@@ -23,3 +14,18 @@ RDoc::Task.new do |rdoc|
23
14
  rdoc.title = 'HasMagicColumns'
24
15
  rdoc.options << '--line-numbers' << '--inline-source'
25
16
  end
17
+
18
+ task :default => :spec
19
+
20
+ desc 'Run specs'
21
+ RSpec::Core::RakeTask.new(:spec) do |t|
22
+ t.pattern = 'spec/**/*_spec.rb'
23
+ t.ruby_opts = "-Ilib:spec"
24
+ end
25
+
26
+ namespace :spec do
27
+ desc "Run Rails specs"
28
+ RSpec::Core::RakeTask.new(:rails) do |t|
29
+ t.pattern = %w'spec/finders/active_record_spec.rb spec/view_helpers/action_view_spec.rb'
30
+ end
31
+ end
@@ -14,7 +14,7 @@ Gem::Specification.new do |s|
14
14
  s.date = Date.today
15
15
 
16
16
  s.files = `git ls-files`.split("\n")
17
- s.test_files = `git ls-files -- {test,spec,features}/`.split("\n")
17
+ s.test_files = `git ls-files -- spec/`.split("\n")
18
18
  s.require_paths = ["lib"]
19
19
 
20
20
  s.add_dependency("activesupport", ["~> 3.0"])
data/init.rb CHANGED
@@ -1 +1,5 @@
1
- ActiveRecord::Base.send :include, Has::Magic::Columns
1
+ require 'has_magic_columns'
2
+
3
+ if defined? ActiveRecord::Base
4
+ require 'has_magic_columns/active_record'
5
+ end
@@ -24,6 +24,6 @@ private
24
24
 
25
25
  def find_magic_option_for(value)
26
26
  magic_column.magic_options.find(:first,
27
- :conditions => ["value = ? or synonym = ?", value, value])
27
+ :conditions => ["value = ? or synonym = ?", value, value]) unless magic_column.nil? or magic_column.magic_options.nil?
28
28
  end
29
29
  end
@@ -1,7 +1,3 @@
1
- require 'has_magic_columns/has_magic_columns'
2
- require 'has_magic_columns/version'
3
- require 'has_magic_columns/railtie' if defined?(Rails)
4
-
5
1
  # Has Magic Columns
6
2
  #
7
3
  # Copyright (c) 2007 Brandon Keene <bkeene AT gmail DOT com>
@@ -22,3 +18,11 @@ require 'has_magic_columns/railtie' if defined?(Rails)
22
18
  module HasMagicColumns # :nodoc:
23
19
  end
24
20
 
21
+ require 'has_magic_columns/version'
22
+
23
+ if defined? Rails::Railtie
24
+ require 'has_magic_columns/railtie'
25
+ elsif defined? Rails::Initializer
26
+ $stderr.puts "\nhas_magic_columns is not compatible with Rails 2, use at your own risk.\n\n"
27
+ end
28
+
@@ -0,0 +1,183 @@
1
+ require 'active_support'
2
+ require 'active_record'
3
+
4
+ module HasMagicColumns #:nodoc:
5
+ module ActiveRecord
6
+ module ClassMethods
7
+ def has_magic_columns(options = {})
8
+ unless magical?
9
+ # Associations
10
+ has_many :magic_attribute_relationships, :as => :owner, :dependent => :destroy
11
+ has_many :magic_attributes, :through => :magic_attribute_relationships, :dependent => :destroy
12
+
13
+ # Eager loading - EXPERIMENTAL!
14
+ if options[:eager]
15
+ class_eval do
16
+ def after_initialize
17
+ initialize_magic_columns
18
+ end
19
+ end
20
+ end
21
+
22
+ # Inheritence
23
+ cattr_accessor :inherited_from
24
+
25
+ # if options[:through] is supplied, treat as an inherited relationship
26
+ if self.inherited_from = options[:through]
27
+ class_eval do
28
+ def inherited_magic_columns
29
+ raise "Cannot inherit MagicColumns from a non-existant association: #{@inherited_from}" unless self.class.method_defined?(inherited_from)# and self.send(inherited_from)
30
+ self.send(inherited_from).magic_columns
31
+ end
32
+ end
33
+ alias_method :magic_columns, :inherited_magic_columns unless method_defined? :magic_columns
34
+
35
+ # otherwise the calling model has the relationships
36
+ else
37
+ has_many :magic_column_relationships, :as => :owner, :dependent => :destroy
38
+ has_many :magic_columns, :through => :magic_column_relationships, :dependent => :destroy
39
+ end
40
+
41
+ # Hook into Base
42
+ class_eval do
43
+ alias_method :reload_without_magic, :reload
44
+ alias_method :create_or_update_without_magic, :create_or_update
45
+ alias_method :read_attribute_without_magic, :read_attribute
46
+ end
47
+ end
48
+ include InstanceMethods
49
+
50
+ # Add Magic to Base
51
+ alias_method :reload, :reload_with_magic
52
+ alias_method :read_attribute, :read_attribute_with_magic
53
+ alias_method :create_or_update, :create_or_update_with_magic
54
+ end
55
+
56
+ def magical?
57
+ self.included_modules.include?(InstanceMethods)
58
+ end
59
+ end
60
+
61
+ module InstanceMethods #:nodoc:
62
+ # Reinitialize MagicColumns and MagicAttributes when Model is reloaded
63
+ def reload_with_magic
64
+ initialize_magic_columns
65
+ reload_without_magic
66
+ end
67
+
68
+ def update_attributes(new_attributes)
69
+ attributes = new_attributes.stringify_keys
70
+ magic_attrs = magic_columns.map(&:name)
71
+
72
+ super(attributes.select{ |k, v| !magic_attrs.include?(k) })
73
+ attributes.select{ |k, v| magic_attrs.include?(k) }.each do |k, v|
74
+ col = find_magic_column_by_name(k)
75
+ attr = find_magic_attribute_by_column(col).first
76
+ attr.update_attributes(:value => v)
77
+ end
78
+ end
79
+
80
+ private
81
+
82
+ # Save MagicAttributes from @attributes
83
+ def create_or_update_with_magic
84
+ if result = create_or_update_without_magic
85
+ magic_columns.each do |column|
86
+ value = @attributes[column.name]
87
+ existing = find_magic_attribute_by_column(column)
88
+
89
+ unless column.datatype == 'check_box_multiple'
90
+ (attr = existing.first) ?
91
+ update_magic_attribute(attr, value) :
92
+ create_magic_attribute(column, value)
93
+ else
94
+ #TODO - make this more efficient
95
+ value = [value] unless value.is_a? Array
96
+ existing.map(&:destroy) if existing
97
+ value.collect {|v| create_magic_attribute(column, v)}
98
+ end
99
+ end
100
+ end
101
+ result
102
+ end
103
+
104
+ # Load (lazily) MagicAttributes or fall back
105
+ def method_missing(method_id, *args)
106
+ super(method_id, *args)
107
+ rescue NoMethodError
108
+ method_name = method_id.to_s
109
+ attr_names = magic_columns.map(&:name)
110
+ initialize_magic_columns and retry if attr_names.include?(method_name) or
111
+ (md = /[\?|\=]/.match(method_name) and
112
+ attr_names.include?(md.pre_match))
113
+ super(method_id, *args)
114
+ end
115
+
116
+ # Load the MagicAttribute(s) associated with attr_name and cast them to proper type.
117
+ def read_attribute_with_magic(attr_name)
118
+ return read_attribute_without_magic(attr_name) if column_for_attribute(attr_name) # filter for regular columns
119
+ attr_name = attr_name.to_s
120
+
121
+ if !(value = @attributes[attr_name]).nil?
122
+ if column = find_magic_column_by_name(attr_name)
123
+ if value.is_a? Array
124
+ value.map {|v| column.type_cast(v)}
125
+ else
126
+ column.type_cast(value)
127
+ end
128
+ else
129
+ value
130
+ end
131
+ else
132
+ nil
133
+ end
134
+ end
135
+
136
+ # Lookup all MagicAttributes and setup @attributes
137
+ def initialize_magic_columns
138
+ magic_columns.each do |column|
139
+ attribute = find_magic_attribute_by_column(column)
140
+ name = column.name
141
+
142
+ # Validation
143
+ self.class.validates_presence_of(name) if column.is_required?
144
+
145
+ # Write attribute
146
+ unless column.datatype == 'check_box_multiple'
147
+ (attr = attribute.first) ?
148
+ write_attribute(name, attr.to_s) :
149
+ write_attribute(name, column.default)
150
+ else
151
+ write_attribute(name, attribute.map(&:to_s))
152
+ end
153
+ end
154
+ end
155
+
156
+ def find_magic_attribute_by_column(column)
157
+ magic_attributes.to_a.find_all {|attr| attr.magic_column_id == column.id}
158
+ end
159
+
160
+ def find_magic_column_by_name(attr_name)
161
+ magic_columns.to_a.find {|column| column.name == attr_name}
162
+ end
163
+
164
+ def create_magic_attribute(magic_column, value)
165
+ magic_attributes << MagicAttribute.create(:magic_column => magic_column, :value => value)
166
+ end
167
+
168
+ def update_magic_attribute(magic_attribute, value)
169
+ magic_attribute.update_attributes(:value => value)
170
+ end
171
+ end
172
+
173
+ # mix into Active Record
174
+ ::ActiveRecord::Base.extend ClassMethods
175
+
176
+ %w{ models }.each do |dir|
177
+ path = File.join(File.dirname(__FILE__), '../app', dir)
178
+ $LOAD_PATH << path
179
+ ActiveSupport::Dependencies.autoload_paths << path
180
+ ActiveSupport::Dependencies.autoload_once_paths.delete(path)
181
+ end
182
+ end
183
+ end
@@ -3,19 +3,8 @@ require 'has_magic_columns'
3
3
  module HasMagicColumns
4
4
  class Railtie < Rails::Railtie
5
5
  initializer "has_magic_columns" do
6
-
7
- # I think this is probably a ghetto way of doing this
8
- # based on some limited research into other plugins. Neverhtless
9
- # it's the only way I know for the moment.
10
6
  ActiveSupport.on_load :active_record do
11
- ActiveRecord::Base.send(:include, HasMagicColumns)
12
-
13
- %w{ models }.each do |dir|
14
- path = File.join(File.dirname(__FILE__), '../app', dir)
15
- $LOAD_PATH << path
16
- ActiveSupport::Dependencies.autoload_paths << path
17
- ActiveSupport::Dependencies.autoload_once_paths.delete(path)
18
- end
7
+ require 'has_magic_columns/active_record'
19
8
  end
20
9
  end
21
10
  end
@@ -1,3 +1,3 @@
1
1
  module HasMagicColumns
2
- VERSION = "0.1.2"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -0,0 +1,23 @@
1
+ sqlite3:
2
+ database: ":memory:"
3
+ adapter: sqlite3
4
+ timeout: 500
5
+
6
+ mysql:
7
+ adapter: mysql
8
+ database: has_magic_columns
9
+ username:
10
+ encoding: utf8
11
+
12
+ mysql2:
13
+ adapter: mysql2
14
+ database: has_magic_columns
15
+ username:
16
+ encoding: utf8
17
+
18
+ postgres:
19
+ adapter: postgresql
20
+ database: has_magic_columns
21
+ username: postgres
22
+ min_messages: warning
23
+
@@ -0,0 +1,113 @@
1
+ require 'active_record'
2
+ require 'active_record/fixtures'
3
+ require 'active_support/multibyte' # needed for Ruby 1.9.1
4
+
5
+ $query_count = 0
6
+ $query_sql = []
7
+
8
+ ignore_sql = /
9
+ ^(
10
+ PRAGMA | SHOW\ max_identifier_length |
11
+ SELECT\ (currval|CAST|@@IDENTITY|@@ROWCOUNT) |
12
+ SHOW\ (FIELDS|TABLES)
13
+ )\b |
14
+ \bFROM\ (sqlite_master|pg_tables|pg_attribute)\b
15
+ /x
16
+
17
+ ActiveSupport::Notifications.subscribe(/^sql\./) do |*args|
18
+ payload = args.last
19
+ unless payload[:name] =~ /^Fixture/ or payload[:sql] =~ ignore_sql
20
+ $query_count += 1
21
+ $query_sql << payload[:sql]
22
+ end
23
+ end
24
+
25
+ module ActiverecordTestConnector
26
+ extend self
27
+
28
+ attr_accessor :able_to_connect
29
+ attr_accessor :connected
30
+
31
+ FIXTURES_PATH = File.expand_path('../../fixtures', __FILE__)
32
+
33
+ Fixtures = defined?(ActiveRecord::Fixtures) ? ActiveRecord::Fixtures : ::Fixtures
34
+
35
+ # Set our defaults
36
+ self.connected = false
37
+ self.able_to_connect = true
38
+
39
+ def setup
40
+ unless self.connected || !self.able_to_connect
41
+ setup_connection
42
+ load_schema
43
+ add_load_path FIXTURES_PATH
44
+ self.connected = true
45
+ end
46
+ rescue Exception => e # errors from ActiveRecord setup
47
+ $stderr.puts "\nSkipping ActiveRecord tests: #{e}\n\n"
48
+ self.able_to_connect = false
49
+ end
50
+
51
+ private
52
+
53
+ def add_load_path(path)
54
+ dep = defined?(ActiveSupport::Dependencies) ? ActiveSupport::Dependencies : ::Dependencies
55
+ dep.autoload_paths.unshift path
56
+ end
57
+
58
+ def setup_connection
59
+ db = ENV['DB'].blank? ? 'sqlite3' : ENV['DB']
60
+
61
+ configurations = YAML.load_file(File.expand_path('../../database.yml', __FILE__))
62
+ raise "no configuration for '#{db}'" unless configurations.key? db
63
+ configuration = configurations[db]
64
+
65
+ # ActiveRecord::Base.logger = Logger.new(STDOUT) if $0 == 'irb'
66
+ puts "using #{configuration['adapter']} adapter"
67
+
68
+ ActiveRecord::Base.configurations = { db => configuration }
69
+ ActiveRecord::Base.establish_connection(db)
70
+ ActiveRecord::Base.default_timezone = :utc
71
+ end
72
+
73
+ def load_schema
74
+ ActiveRecord::Base.silence do
75
+ ActiveRecord::Migration.verbose = false
76
+ load File.join(FIXTURES_PATH, 'schema.rb')
77
+ end
78
+ end
79
+
80
+ module FixtureSetup
81
+ def fixtures(*tables)
82
+ table_names = tables.map { |t| t.to_s }
83
+
84
+ fixtures = Fixtures.create_fixtures ActiverecordTestConnector::FIXTURES_PATH, table_names
85
+ @@loaded_fixtures = {}
86
+ @@fixture_cache = {}
87
+
88
+ unless fixtures.nil?
89
+ if fixtures.instance_of?(Fixtures)
90
+ @@loaded_fixtures[fixtures.table_name] = fixtures
91
+ else
92
+ fixtures.each { |f| @@loaded_fixtures[f.table_name] = f }
93
+ end
94
+ end
95
+
96
+ table_names.each do |table_name|
97
+ define_method(table_name) do |*fixtures|
98
+ @@fixture_cache[table_name] ||= {}
99
+
100
+ instances = fixtures.map do |fixture|
101
+ if @@loaded_fixtures[table_name][fixture.to_s]
102
+ @@fixture_cache[table_name][fixture] ||= @@loaded_fixtures[table_name][fixture.to_s].find
103
+ else
104
+ raise StandardError, "No fixture with name '#{fixture}' found for table '#{table_name}'"
105
+ end
106
+ end
107
+
108
+ instances.size == 1 ? instances.first : instances
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,7 @@
1
+ # Account class tests the case where has_magic_columns is used in a parent-
2
+ # child fashion where the Account model has the magic columns and the User
3
+ # model inherits them through the associated Account.
4
+ class Account < ActiveRecord::Base
5
+ has_many :users
6
+ has_magic_columns
7
+ end
@@ -0,0 +1,2 @@
1
+ important:
2
+ name: "Important Account"
@@ -0,0 +1,5 @@
1
+ charlie:
2
+ name: "Charlie"
3
+
4
+ denise:
5
+ name: "Denise"
@@ -0,0 +1,5 @@
1
+ # Person class tests the case where has_magic_columns is used on a model
2
+ # without the parent-child relationship.
3
+ class Person < ActiveRecord::Base
4
+ has_magic_columns
5
+ end
@@ -0,0 +1,61 @@
1
+ ActiveRecord::Schema.define do
2
+
3
+ create_table "users", :force => true do |t|
4
+ t.column "name", :text
5
+ t.column "account_id", :integer
6
+ end
7
+
8
+ create_table "accounts", :force => true do |t|
9
+ t.column "name", :text
10
+ end
11
+
12
+ create_table "people", :force => true do |t|
13
+ t.column "name", :text
14
+ end
15
+
16
+
17
+ # Has Magic Columns migration generator output
18
+
19
+ create_table :magic_columns do |t|
20
+ t.column :name, :string
21
+ t.column :pretty_name, :string
22
+ t.column :datatype, :string, :default => "string"
23
+ t.column :default, :string
24
+ t.column :is_required, :boolean, :default => false
25
+ t.column :include_blank, :boolean, :default => false
26
+ t.column :allow_other, :boolean, :default => true
27
+ t.column :created_at, :datetime
28
+ t.column :updated_at, :datetime
29
+ end
30
+
31
+ create_table :magic_attributes do |t|
32
+ t.column :magic_column_id, :integer
33
+ t.column :magic_option_id, :integer
34
+ t.column :value, :string
35
+ t.column :created_at, :datetime
36
+ t.column :updated_at, :datetime
37
+ end
38
+
39
+ create_table :magic_options do |t|
40
+ t.column :magic_column_id, :integer
41
+ t.column :value, :string
42
+ t.column :synonym, :string
43
+ t.column :created_at, :datetime
44
+ t.column :updated_at, :datetime
45
+ end
46
+
47
+ create_table :magic_column_relationships do |t|
48
+ t.column :magic_column_id, :integer
49
+ t.column :owner_id, :integer
50
+ t.column :owner_type, :string
51
+ t.column :created_at, :datetime
52
+ t.column :updated_at, :datetime
53
+ end
54
+
55
+ create_table :magic_attribute_relationships do |t|
56
+ t.column :magic_attribute_id, :integer
57
+ t.column :owner_id, :integer
58
+ t.column :owner_type, :string
59
+ end
60
+
61
+ end
@@ -0,0 +1,7 @@
1
+ # User class tests the case where has_magic_columns is used in a parent-
2
+ # child fashion where the Account model has the magic columns and the User
3
+ # model inherits them through the associated Account.
4
+ class User < ActiveRecord::Base
5
+ belongs_to :account
6
+ has_magic_columns :through => :account
7
+ end
@@ -0,0 +1,7 @@
1
+ alice:
2
+ name: "Alice"
3
+ account: important
4
+
5
+ bob:
6
+ name: "Bob"
7
+ account: important
@@ -0,0 +1,112 @@
1
+ require 'spec_helper'
2
+ require 'has_magic_columns/active_record'
3
+ require 'finders/activerecord_test_connector'
4
+
5
+ ActiverecordTestConnector.setup
6
+ abort unless ActiverecordTestConnector.able_to_connect
7
+
8
+ describe HasMagicColumns do
9
+ extend ActiverecordTestConnector::FixtureSetup
10
+
11
+ fixtures :people, :accounts, :users
12
+
13
+ context "on a single model" do
14
+ before(:each) do
15
+ @charlie = people(:charlie)
16
+ end
17
+
18
+ it "initializes magic columns correctly" do
19
+ @charlie.should_not be_nil
20
+ @charlie.class.should be(Person)
21
+ @charlie.magic_columns.should_not be_nil
22
+ end
23
+
24
+ it "allows adding a magic column" do
25
+ @charlie.magic_columns.create(:name => 'salary')
26
+ @charlie.magic_columns.length.should be(1)
27
+ end
28
+
29
+ it "allows setting and saving of magic attributes" do
30
+ @charlie.magic_columns.create(:name => 'salary')
31
+ @charlie.salary = 50000
32
+ @charlie.save
33
+ @charlie = Person.find(people(:charlie).id)
34
+ @charlie.salary.should_not be_nil
35
+ end
36
+
37
+ #it "forces required if is_required is true" do
38
+ # # TODO figure out why this fails
39
+ # @charlie.magic_columns.create(:name => "last_name", :is_required => true)
40
+ # @charlie.save.should be_false
41
+ #end
42
+
43
+ it "allows datatype to be :date" do
44
+ @charlie.magic_columns.create(:name => "birthday", :datatype => :date)
45
+ @charlie.birthday = Date.today
46
+ @charlie.save.should be_true
47
+ end
48
+
49
+ it "allows datatype to be :datetime" do
50
+ @charlie.magic_columns.create(:name => "signed_up_at", :datatype => :datetime)
51
+ @charlie.signed_up_at = DateTime.now
52
+ @charlie.save.should be_true
53
+ end
54
+
55
+ it "allows datatype to be :integer" do
56
+ @charlie.magic_columns.create(:name => "age", :datatype => :integer)
57
+ @charlie.age = 5
58
+ @charlie.save.should be_true
59
+ end
60
+
61
+ it "allows datatype to be :check_box_boolean" do
62
+ @charlie.magic_columns.create(:name => "retired", :datatype => :check_box_boolean)
63
+ @charlie.retired = false
64
+ @charlie.save.should be_true
65
+ end
66
+
67
+ it "allows default to be set" do
68
+ @charlie.magic_columns.create(:name => "bonus", :default => "40000")
69
+ @charlie.bonus.should == "40000"
70
+ end
71
+
72
+ it "allows a pretty display name to be set" do
73
+ @charlie.magic_columns.create(:name => "zip", :pretty_name => "Zip Code")
74
+ @charlie.magic_columns.last.pretty_name.should == "Zip Code"
75
+ end
76
+ end
77
+
78
+ context "in a parent-child relationship" do
79
+ before(:each) do
80
+ @alice = users(:alice)
81
+ @account = accounts(:important)
82
+ end
83
+
84
+ it "initializes magic columns correctly" do
85
+ @alice.should_not be_nil
86
+ @alice.class.should be(User)
87
+ @alice.magic_columns.should_not be_nil
88
+
89
+ @account.should_not be_nil
90
+ @account.class.should be(Account)
91
+ @alice.magic_columns.should_not be_nil
92
+ end
93
+
94
+ it "allows adding a magic column to the child" do
95
+ @alice.magic_columns.create(:name => 'salary')
96
+ lambda{@alice.salary}.should_not raise_error
97
+ lambda{@account.reload_with_magic.salary}.should_not raise_error
98
+ end
99
+
100
+ it "allows adding a magic column to the parent" do
101
+ @account.magic_columns.create(:name => 'age')
102
+ lambda{@alice.reload_with_magic.age}.should_not raise_error
103
+ end
104
+
105
+ it "sets magic columns for all child models" do
106
+ @bob = users(:bob)
107
+ @bob.magic_columns.create(:name => 'birthday')
108
+ @alice.reload_with_magic
109
+ lambda{@alice.birthday}.should_not raise_error
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,14 @@
1
+ require 'rspec'
2
+ begin
3
+ require 'ruby-debug'
4
+ rescue LoadError
5
+ # no debugger available
6
+ end
7
+
8
+ RSpec.configure do |config|
9
+ # config.include My::Pony, My::Horse, :type => :farm
10
+ # config.include MyExtras
11
+ # config.predicate_matchers[:swim] = :can_swim?
12
+ # config.mock_with :mocha
13
+ end
14
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: has_magic_columns
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -11,11 +11,11 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2011-09-06 00:00:00.000000000Z
14
+ date: 2011-09-08 00:00:00.000000000Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: activesupport
18
- requirement: &80667260 !ruby/object:Gem::Requirement
18
+ requirement: &73612880 !ruby/object:Gem::Requirement
19
19
  none: false
20
20
  requirements:
21
21
  - - ~>
@@ -23,10 +23,10 @@ dependencies:
23
23
  version: '3.0'
24
24
  type: :runtime
25
25
  prerelease: false
26
- version_requirements: *80667260
26
+ version_requirements: *73612880
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: activerecord
29
- requirement: &80666990 !ruby/object:Gem::Requirement
29
+ requirement: &73612610 !ruby/object:Gem::Requirement
30
30
  none: false
31
31
  requirements:
32
32
  - - ~>
@@ -34,10 +34,10 @@ dependencies:
34
34
  version: '3.0'
35
35
  type: :runtime
36
36
  prerelease: false
37
- version_requirements: *80666990
37
+ version_requirements: *73612610
38
38
  - !ruby/object:Gem::Dependency
39
39
  name: rails
40
- requirement: &80666750 !ruby/object:Gem::Requirement
40
+ requirement: &73612370 !ruby/object:Gem::Requirement
41
41
  none: false
42
42
  requirements:
43
43
  - - ~>
@@ -45,10 +45,10 @@ dependencies:
45
45
  version: '3.0'
46
46
  type: :development
47
47
  prerelease: false
48
- version_requirements: *80666750
48
+ version_requirements: *73612370
49
49
  - !ruby/object:Gem::Dependency
50
50
  name: sqlite3
51
- requirement: &80666560 !ruby/object:Gem::Requirement
51
+ requirement: &73612180 !ruby/object:Gem::Requirement
52
52
  none: false
53
53
  requirements:
54
54
  - - ! '>='
@@ -56,10 +56,10 @@ dependencies:
56
56
  version: '0'
57
57
  type: :development
58
58
  prerelease: false
59
- version_requirements: *80666560
59
+ version_requirements: *73612180
60
60
  - !ruby/object:Gem::Dependency
61
61
  name: rspec
62
- requirement: &80666280 !ruby/object:Gem::Requirement
62
+ requirement: &73611900 !ruby/object:Gem::Requirement
63
63
  none: false
64
64
  requirements:
65
65
  - - ~>
@@ -67,10 +67,10 @@ dependencies:
67
67
  version: '2.0'
68
68
  type: :development
69
69
  prerelease: false
70
- version_requirements: *80666280
70
+ version_requirements: *73611900
71
71
  - !ruby/object:Gem::Dependency
72
72
  name: rdoc
73
- requirement: &80666070 !ruby/object:Gem::Requirement
73
+ requirement: &73611670 !ruby/object:Gem::Requirement
74
74
  none: false
75
75
  requirements:
76
76
  - - ! '>='
@@ -78,7 +78,7 @@ dependencies:
78
78
  version: '0'
79
79
  type: :development
80
80
  prerelease: false
81
- version_requirements: *80666070
81
+ version_requirements: *73611670
82
82
  description: Allow addition of custom 'magic' columns to ActiveRecord models.
83
83
  email:
84
84
  - latortuga@gmail.com
@@ -88,6 +88,7 @@ extensions: []
88
88
  extra_rdoc_files: []
89
89
  files:
90
90
  - .gitignore
91
+ - .rspec
91
92
  - CONTRIBUTORS.md
92
93
  - Gemfile
93
94
  - LICENSE
@@ -104,10 +105,20 @@ files:
104
105
  - lib/generators/has_magic_columns/install/install_generator.rb
105
106
  - lib/generators/has_magic_columns/install/templates/migration.rb
106
107
  - lib/has_magic_columns.rb
107
- - lib/has_magic_columns/has_magic_columns.rb
108
+ - lib/has_magic_columns/active_record.rb
108
109
  - lib/has_magic_columns/railtie.rb
109
110
  - lib/has_magic_columns/version.rb
110
- - test/has_magic_columns_test.rb
111
+ - spec/database.yml
112
+ - spec/finders/activerecord_test_connector.rb
113
+ - spec/fixtures/account.rb
114
+ - spec/fixtures/accounts.yml
115
+ - spec/fixtures/people.yml
116
+ - spec/fixtures/person.rb
117
+ - spec/fixtures/schema.rb
118
+ - spec/fixtures/user.rb
119
+ - spec/fixtures/users.yml
120
+ - spec/has_magic_columns_spec.rb
121
+ - spec/spec_helper.rb
111
122
  homepage: https://github.com/latortuga/has_magic_columns
112
123
  licenses: []
113
124
  post_install_message:
@@ -132,4 +143,15 @@ rubygems_version: 1.8.0
132
143
  signing_key:
133
144
  specification_version: 3
134
145
  summary: Custom fields for Rails 3
135
- test_files: []
146
+ test_files:
147
+ - spec/database.yml
148
+ - spec/finders/activerecord_test_connector.rb
149
+ - spec/fixtures/account.rb
150
+ - spec/fixtures/accounts.yml
151
+ - spec/fixtures/people.yml
152
+ - spec/fixtures/person.rb
153
+ - spec/fixtures/schema.rb
154
+ - spec/fixtures/user.rb
155
+ - spec/fixtures/users.yml
156
+ - spec/has_magic_columns_spec.rb
157
+ - spec/spec_helper.rb
@@ -1,172 +0,0 @@
1
- module HasMagicColumns #:nodoc:
2
- def self.included(base) # :nodoc:
3
- base.extend ClassMethods
4
- end
5
-
6
- module ClassMethods
7
- def has_magic_columns(options = {})
8
- unless magical?
9
- # Associations
10
- has_many :magic_attribute_relationships, :as => :owner, :dependent => :destroy
11
- has_many :magic_attributes, :through => :magic_attribute_relationships, :dependent => :destroy
12
-
13
- # Eager loading - EXPERIMENTAL!
14
- if options[:eager]
15
- class_eval do
16
- def after_initialize
17
- initialize_magic_columns
18
- end
19
- end
20
- end
21
-
22
- # Inheritence
23
- cattr_accessor :inherited_from
24
-
25
- # if options[:through] is supplied, treat as an inherited relationship
26
- if self.inherited_from = options[:through]
27
- class_eval do
28
- def inherited_magic_columns
29
- raise "Cannot inherit MagicColumns from a non-existant association: #{@inherited_from}" unless self.class.method_defined?(inherited_from)# and self.send(inherited_from)
30
- self.send(inherited_from).magic_columns
31
- end
32
- end
33
- alias_method :magic_columns, :inherited_magic_columns unless method_defined? :magic_columns
34
-
35
- # otherwise the calling model has the relationships
36
- else
37
- has_many :magic_column_relationships, :as => :owner, :dependent => :destroy
38
- has_many :magic_columns, :through => :magic_column_relationships, :dependent => :destroy
39
- end
40
-
41
- # Hook into Base
42
- class_eval do
43
- alias_method :reload_without_magic, :reload
44
- alias_method :create_or_update_without_magic, :create_or_update
45
- alias_method :read_attribute_without_magic, :read_attribute
46
- end
47
- end
48
- include InstanceMethods
49
-
50
- # Add Magic to Base
51
- alias_method :reload, :reload_with_magic
52
- alias_method :read_attribute, :read_attribute_with_magic
53
- alias_method :create_or_update, :create_or_update_with_magic
54
- end
55
-
56
- def magical?
57
- self.included_modules.include?(InstanceMethods)
58
- end
59
- end
60
-
61
- module InstanceMethods #:nodoc:
62
- # Reinitialize MagicColumns and MagicAttributes when Model is reloaded
63
- def reload_with_magic
64
- initialize_magic_columns
65
- reload_without_magic
66
- end
67
-
68
- def update_attributes(new_attributes)
69
- attributes = new_attributes.stringify_keys
70
- magic_attrs = magic_columns.map(&:name)
71
-
72
- super(attributes.select{ |k, v| !magic_attrs.include?(k) })
73
- attributes.select{ |k, v| magic_attrs.include?(k) }.each do |k, v|
74
- col = find_magic_column_by_name(k)
75
- attr = find_magic_attribute_by_column(col).first
76
- attr.update_attributes(:value => v)
77
- end
78
- end
79
-
80
- private
81
-
82
- # Save MagicAttributes from @attributes
83
- def create_or_update_with_magic
84
- if result = create_or_update_without_magic
85
- magic_columns.each do |column|
86
- value = @attributes[column.name]
87
- existing = find_magic_attribute_by_column(column)
88
-
89
- unless column.datatype == 'check_box_multiple'
90
- (attr = existing.first) ?
91
- update_magic_attribute(attr, value) :
92
- create_magic_attribute(column, value)
93
- else
94
- #TODO - make this more efficient
95
- value = [value] unless value.is_a? Array
96
- existing.map(&:destroy) if existing
97
- value.collect {|v| create_magic_attribute(column, v)}
98
- end
99
- end
100
- end
101
- result
102
- end
103
-
104
- # Load (lazily) MagicAttributes or fall back
105
- def method_missing(method_id, *args)
106
- super(method_id, *args)
107
- rescue NoMethodError
108
- method_name = method_id.to_s
109
- attr_names = magic_columns.map(&:name)
110
- initialize_magic_columns and retry if attr_names.include?(method_name) or
111
- (md = /[\?|\=]/.match(method_name) and
112
- attr_names.include?(md.pre_match))
113
- super(method_id, *args)
114
- end
115
-
116
- # Load the MagicAttribute(s) associated with attr_name and cast them to proper type.
117
- def read_attribute_with_magic(attr_name)
118
- return read_attribute_without_magic(attr_name) if column_for_attribute(attr_name) # filter for regular columns
119
- attr_name = attr_name.to_s
120
-
121
- if !(value = @attributes[attr_name]).nil?
122
- if column = find_magic_column_by_name(attr_name)
123
- if value.is_a? Array
124
- value.map {|v| column.type_cast(v)}
125
- else
126
- column.type_cast(value)
127
- end
128
- else
129
- value
130
- end
131
- else
132
- nil
133
- end
134
- end
135
-
136
- # Lookup all MagicAttributes and setup @attributes
137
- def initialize_magic_columns
138
- magic_columns.each do |column|
139
- attribute = find_magic_attribute_by_column(column)
140
- name = column.name
141
-
142
- # Validation
143
- self.class.validates_presence_of(name) if column.is_required?
144
-
145
- # Write attribute
146
- unless column.datatype == 'check_box_multiple'
147
- (attr = attribute.first) ?
148
- write_attribute(name, attr.to_s) :
149
- write_attribute(name, column.default)
150
- else
151
- write_attribute(name, attribute.map(&:to_s))
152
- end
153
- end
154
- end
155
-
156
- def find_magic_attribute_by_column(column)
157
- magic_attributes.to_a.find_all {|attr| attr.magic_column_id == column.id}
158
- end
159
-
160
- def find_magic_column_by_name(attr_name)
161
- magic_columns.to_a.find {|column| column.name == attr_name}
162
- end
163
-
164
- def create_magic_attribute(magic_column, value)
165
- magic_attributes << MagicAttribute.create(:magic_column => magic_column, :value => value)
166
- end
167
-
168
- def update_magic_attribute(magic_attribute, value)
169
- magic_attribute.update_attributes(:value => value)
170
- end
171
- end
172
- end
@@ -1,8 +0,0 @@
1
- require 'test/unit'
2
-
3
- class HasMagicColumnsTest < Test::Unit::TestCase
4
- # Replace this with your real tests.
5
- def test_this_plugin
6
- flunk
7
- end
8
- end