has_magic_columns 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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