has_dynamic_fields 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.1
1
+ 0.0.2
@@ -0,0 +1,85 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "has_dynamic_fields"
8
+ s.version = "0.0.2"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Jason Ayre"]
12
+ s.date = "2012-04-16"
13
+ s.description = "Lets your models act dynamic in a clean EAV style"
14
+ s.email = "jasonayre@gmail.com"
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ "Gemfile",
22
+ "Gemfile.lock",
23
+ "LICENSE.txt",
24
+ "README.rdoc",
25
+ "Rakefile",
26
+ "VERSION",
27
+ "has_dynamic_fields.gemspec",
28
+ "lib/generators/dynamic_field_migration_generator.rb",
29
+ "lib/generators/dynamic_field_scaffold_generator.rb",
30
+ "lib/generators/templates/add_field_migration.rb",
31
+ "lib/generators/templates/fieldgroup_model_template.rb",
32
+ "lib/generators/templates/fieldgroups_table_migration.rb",
33
+ "lib/generators/templates/fieldoptions_model_template.rb",
34
+ "lib/generators/templates/fieldoptions_table_migration.rb",
35
+ "lib/generators/templates/fields_model_template.rb",
36
+ "lib/generators/templates/fields_table_migration.rb",
37
+ "lib/generators/templates/fieldvalues_model_template.rb",
38
+ "lib/generators/templates/fieldvalues_table_migration.rb",
39
+ "lib/generators/templates/remove_field_migration.rb",
40
+ "lib/has_dynamic_fields.rb",
41
+ "lib/has_dynamic_fields/base.rb",
42
+ "lib/has_dynamic_fields/has_dynamic_fields.rb",
43
+ "lib/has_dynamic_fields/railtie.rb",
44
+ "lib/tasks/dynamic_field_migrate.rake",
45
+ "spec/spec_helper.rb",
46
+ "test/helper.rb",
47
+ "test/test_has_dynamic_fields.rb"
48
+ ]
49
+ s.homepage = "http://github.com/jasonayre/has_dynamic_fields"
50
+ s.licenses = ["MIT"]
51
+ s.require_paths = ["lib"]
52
+ s.rubygems_version = "1.8.22"
53
+ s.summary = "Lets your models act dynamically in a clean EAV style"
54
+
55
+ if s.respond_to? :specification_version then
56
+ s.specification_version = 3
57
+
58
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
59
+ s.add_runtime_dependency(%q<seed-fu>, [">= 0"])
60
+ s.add_development_dependency(%q<rspec>, ["~> 2.8.0"])
61
+ s.add_development_dependency(%q<shoulda>, [">= 0"])
62
+ s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
63
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
64
+ s.add_development_dependency(%q<jeweler>, ["~> 1.8.3"])
65
+ s.add_development_dependency(%q<simplecov>, [">= 0"])
66
+ else
67
+ s.add_dependency(%q<seed-fu>, [">= 0"])
68
+ s.add_dependency(%q<rspec>, ["~> 2.8.0"])
69
+ s.add_dependency(%q<shoulda>, [">= 0"])
70
+ s.add_dependency(%q<rdoc>, ["~> 3.12"])
71
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
72
+ s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
73
+ s.add_dependency(%q<simplecov>, [">= 0"])
74
+ end
75
+ else
76
+ s.add_dependency(%q<seed-fu>, [">= 0"])
77
+ s.add_dependency(%q<rspec>, ["~> 2.8.0"])
78
+ s.add_dependency(%q<shoulda>, [">= 0"])
79
+ s.add_dependency(%q<rdoc>, ["~> 3.12"])
80
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
81
+ s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
82
+ s.add_dependency(%q<simplecov>, [">= 0"])
83
+ end
84
+ end
85
+
@@ -0,0 +1,2 @@
1
+ # require 'has_dynamic_fields/base'
2
+ require 'has_dynamic_fields/railtie'
@@ -0,0 +1,14 @@
1
+ module HasDynamicFields
2
+
3
+ module Base
4
+ def has_dynamic_fields
5
+ include InstanceMethods
6
+ end
7
+
8
+ module InstanceMethods
9
+
10
+ end
11
+ end
12
+
13
+ end
14
+
@@ -0,0 +1,297 @@
1
+ require "active_record"
2
+ module HasDynamicFields
3
+
4
+ module Base
5
+ def has_dynamic_fields(options = {})
6
+
7
+ options[:entity_class_name] ||= "Entity"
8
+ options[:entity_table_name] ||= options[:entity_class_name].tableize
9
+ options[:entity_foreign_key] ||= "#{options[:entity_class_name].tableize.singularize}_id".to_sym
10
+ options[:entity_singular] ||= options[:entity_class_name].tableize.singularize
11
+ options[:entity_plural] ||= options[:entity_class_name].tableize
12
+
13
+ options[:value_class_name] ||= "DynamicFieldValue"
14
+ options[:value_table_name] ||= options[:value_class_name].tableize
15
+ options[:value_foreign_key] ||= options[:value_class_name].tableize.singularize.to_sym
16
+ options[:value_singular] ||= options[:value_class_name].tableize.singularize
17
+ options[:value_plural] ||= options[:value_class_name].tableize
18
+
19
+ options[:field_class_name] ||= "DynamicField"
20
+ options[:field_table_name] ||= options[:field_class_name].tableize
21
+ options[:field_foreign_key] ||= "#{options[:field_class_name].tableize.singularize}_id".to_sym
22
+ options[:field_singular] ||= options[:field_class_name].tableize.singularize
23
+ options[:field_plural] ||= options[:field_class_name].tableize
24
+
25
+ options[:fieldgroup_class_name] ||= "DynamicFieldGroup"
26
+ options[:fieldgroup_table_name] ||= options[:fieldgroup_class_name].tableize
27
+ options[:fieldgroup_foreign_key] ||= "#{options[:fieldgroup_class_name].tableize.singularize}_id".to_sym
28
+ options[:fieldgroup_singular] ||= options[:fieldgroup_class_name].tableize.singularize
29
+ options[:fieldgroup_plural] ||= options[:fieldgroup_class_name].tableize
30
+
31
+ options[:fieldoptions_class_name] ||= "DynamicFieldOptions"
32
+ options[:fieldoptions_table_name] ||= options[:fieldoptions_class_name].tableize
33
+ options[:fieldoptions_foreign_key] ||= "#{options[:fieldoptions_class_name].tableize.singularize}_id".to_sym
34
+ options[:fieldoptions_singular] ||= options[:fieldoptions_class_name].tableize.singularize
35
+ options[:fieldoptions_plural] ||= options[:fieldoptions_class_name].tableize
36
+
37
+ options[:entity_klass] = options[:entity_class_name].constantize
38
+ options[:field_klass] = options[:field_class_name].constantize
39
+ options[:value_klass] = options[:value_class_name].constantize
40
+ options[:fieldgroup_klass] = options[:fieldgroup_class_name].constantize
41
+ options[:fieldoptions_klass] = options[:fieldoptions_class_name].constantize
42
+
43
+
44
+ cattr_accessor :aad_options
45
+ self.aad_options ||= Hash.new
46
+
47
+ # Return if already present
48
+ return if self.aad_options.keys.include? options[:entity_class_name]
49
+
50
+ self.aad_options = options
51
+
52
+ include InstanceMethods
53
+
54
+ entity_klass = options[:entity_class_name].constantize
55
+ field_klass = options[:field_class_name].constantize
56
+ value_klass = options[:value_class_name].constantize
57
+ fieldgroup_klass = options[:fieldgroup_class_name].constantize
58
+ fieldoptions_klass = options[:fieldoptions_class_name].constantize
59
+
60
+ #eval entity class
61
+ class_eval do
62
+
63
+ #this can be confusing, because I chose to use a horizontal scaling eav pattern due to file upload/carrierwave,
64
+ #this may not make a ton of sense if you look at it out of context and without looking @ your db.
65
+ #entity has one value, but that value row contains all the possible field values, entity type specific field values..
66
+ #are controlled through fieldgroup, each entity has 1 fieldgroup (category,channel,type, whatever), and 1 value row
67
+
68
+ has_one options[:value_singular].to_sym
69
+ accepts_nested_attributes_for options[:value_singular].to_sym, :allow_destroy => true
70
+ belongs_to options[:fieldgroup_singular].to_sym
71
+
72
+ #because field relationships are not automatically saved, we need to set flag then do it explicitly
73
+ before_update :update_dynamic_fields, :if => :update_dynamic_fields?
74
+ before_create :create_dynamic_field_value
75
+
76
+ end
77
+
78
+ #eval value class
79
+ value_klass.class_eval do
80
+
81
+ if (ActiveRecord::Base.connection.table_exists?(options[:field_table_name]) && ActiveRecord::Base.connection.table_exists?(options[:value_table_name]))
82
+
83
+ #mount file fields if carrierwave being used
84
+ if value_klass.send(:respond_to?, :mount_uploader)
85
+ @file_fields = field_klass.where(:fieldtype => "file")
86
+ @file_fields.each do |field|
87
+ mount_uploader "field_#{field.id}".to_sym, "#{value_klass.name}Uploader".constantize
88
+ end
89
+ end
90
+ end
91
+
92
+ belongs_to options[:entity_singular].to_sym
93
+ belongs_to options[:fieldgroup_singular].to_sym
94
+
95
+ end
96
+
97
+ field_klass.class_eval do
98
+
99
+ #note your field model must have at least a column or method called name
100
+ before_create :format_name
101
+ after_create :dynamic_field_add_column_migration
102
+ before_destroy :dynamic_field_remove_column_migration
103
+
104
+ has_many options[:fieldoptions_plural].to_sym
105
+
106
+ belongs_to options[:fieldgroup_singular].to_sym
107
+
108
+ cattr_accessor :aad_options
109
+ self.aad_options = options
110
+
111
+ def dynamic_field_add_column_migration
112
+
113
+ except ||= %w{created_at updated_at}
114
+ except_column_types = [:decimal, :date, :datetime]
115
+ except << self.class.name.constantize.columns.collect {|k| k.name if except_column_types.include?(k.type) }.reject { |val| val == nil }
116
+
117
+ except.flatten!
118
+
119
+ `rails g dynamic_field_migration add_field_#{self.id}_to_#{self.aad_options[:value_table_name]} field_#{self.id}:string`
120
+
121
+ SeedFu::Writer.write("db/fixtures/#{self.aad_options[:field_table_name]}.rb", :class_name => self.aad_options[:field_class_name], :constraints => [:name, self.aad_options[:fieldgroup_foreign_key]]) do |writer|
122
+ self.class.name.constantize.all.each do |f|
123
+ @attrs = f.attributes.reject { |k,v| except.include?(k) }
124
+ writer.add(@attrs)
125
+ end
126
+ end
127
+
128
+ end
129
+
130
+ def dynamic_field_remove_column_migration
131
+
132
+ except ||= %w{created_at updated_at}
133
+ except_column_types = [:decimal, :date, :datetime]
134
+ except << self.class.name.constantize.columns.collect {|k| k.name if except_column_types.include?(k.type) }.reject { |val| val == nil }
135
+
136
+ except.flatten!
137
+
138
+ `rails g dynamic_field_migration remove_field_#{self.id}_from_#{self.aad_options[:value_table_name]} field_#{self.id}`
139
+ `rake db:migrate_dynamic_fields`
140
+ SeedFu::Writer.write("db/fixtures/#{self.aad_options[:field_table_name]}.rb", :class_name => self.aad_options[:field_class_name], :constraints => [:name, self.aad_options[:fieldgroup_foreign_key]]) do |writer|
141
+ self.class.name.constantize.all.each do |f|
142
+ @attrs = f.attributes.reject { |k,v| except.include?(k) }
143
+ writer.add(@attrs)
144
+ end
145
+ end
146
+ end
147
+
148
+ def format_name
149
+ self.name = self.name.split(" ").join("_").downcase
150
+ end
151
+
152
+ def write_seed_data
153
+ SeedFu::Writer.write("db/fixtures/#{self.aad_options[:field_table_name]}.rb", :class_name => self.aad_options[:field_class_name], :constraints => [:name, self.aad_options[:fieldgroup_foreign_key]]) do |writer|
154
+ self.class.name.constantize.all.each do |f|
155
+ @attrs = f.attributes.reject { |k,v| except.include?(k) }
156
+ writer.add(@attrs)
157
+ end
158
+ end
159
+ end
160
+
161
+ end
162
+
163
+
164
+ fieldoptions_klass.class_eval do
165
+
166
+ belongs_to options[:field_singular].to_sym
167
+ after_update :write_seed_data
168
+
169
+ if self.respond_to? :mount_uploader
170
+ mount_uploader :image, "#{options[:fieldoptions_class_name]}Uploader".constantize
171
+ end
172
+
173
+ cattr_accessor :aad_options
174
+ self.aad_options = options
175
+
176
+ def write_seed_data
177
+
178
+ except ||= %w{created_at updated_at}
179
+ except_column_types = [:decimal, :date, :datetime]
180
+ except << self.class.name.constantize.columns.collect {|k| k.name if except_column_types.include?(k.type) }.reject { |val| val == nil }
181
+
182
+ except.flatten!
183
+
184
+ SeedFu::Writer.write("db/fixtures/#{self.aad_options[:fieldoptions_table_name]}.rb", :class_name => self.aad_options[:fieldoptions_class_name], :constraints => [:value, self.aad_options[:field_foreign_key]]) do |writer|
185
+ self.class.name.constantize.all.each do |f|
186
+ @attrs = f.attributes.reject { |k,v| except.include?(k) }
187
+ writer.add(@attrs)
188
+ end
189
+
190
+ end
191
+ end
192
+
193
+ end
194
+
195
+ # todo: make fieldgroup optional
196
+ fieldgroup_klass.class_eval do
197
+ has_many options[:field_table_name].to_sym
198
+ has_many options[:value_table_name].to_sym, :through => options[:field_table_name].to_sym, :source => options[:entity_singular].to_sym
199
+ end
200
+
201
+ end
202
+
203
+ module InstanceMethods
204
+
205
+ def show_options
206
+ puts self.class.aad_options.inspect
207
+ puts self.aad_options.inspect
208
+ end
209
+
210
+ def update_dynamic_fields?
211
+ instance_variable_get("@update_dynamic_fields")
212
+ end
213
+
214
+ def update_dynamic_fields=(val)
215
+ instance_variable_set("@update_dynamic_fields",val)
216
+ end
217
+
218
+ def update_dynamic_fields
219
+ self.send(self.aad_options[:value_singular].to_sym).save
220
+ end
221
+
222
+ def create_dynamic_field_value
223
+ #save relationship upon initial creation, so that fieldgroup specific fields can now be entered
224
+ #todo: make the whole fieldgroup optional for simple talbles
225
+
226
+ value_model = self.aad_options[:value_klass]
227
+ value_model = value_model.create!({ self.aad_options[:fieldgroup_singular].to_sym => self.send(self.aad_options[:fieldgroup_singular]) })
228
+
229
+ self.send("#{self.aad_options[:value_singular]}=".to_sym, value_model)
230
+ end
231
+
232
+ def dynamic_field_keys
233
+ #todo: rename this dynamic_fieldgroup_field_keys for simple tables
234
+ return if self.send(self.aad_options[:fieldgroup_singular].to_sym).blank?
235
+ keys = []
236
+ dynamic_fieldgroup_field_keys = self.send(self.aad_options[:fieldgroup_singular].to_sym).send(self.aad_options[:field_plural].to_sym)
237
+ dynamic_fieldgroup_field_keys.each do |o|
238
+ keys << o.name.to_sym
239
+ end
240
+ keys
241
+ end
242
+
243
+ def dynamic_field_inputs
244
+ fields = []
245
+ dynamic_fields = self.send(self.aad_options[:fieldgroup_singular].to_sym).send(self.aad_options[:field_plural].to_sym)
246
+ dynamic_fields.each do |field|
247
+ fields << field
248
+ end
249
+ end
250
+
251
+ def method_missing(name, *args)
252
+
253
+ return if self.send(self.aad_options[:fieldgroup_singular].to_sym).blank?
254
+ m = name.to_s
255
+ type = :reader
256
+ attribute_name = name.to_s.sub(/=$/) do
257
+ type = :writer
258
+ ""
259
+ end
260
+
261
+ fieldgroup_fields = self.send(self.aad_options[:fieldgroup_singular].to_sym).send(self.aad_options[:field_plural].to_sym)
262
+ @dynamic_field_keys ||= instance_variable_set("@dynamic_field_keys", fieldgroup_fields.collect {|o| o.name.to_sym })
263
+ @dynamic_fields ||= instance_variable_set("@dynamic_fields", fieldgroup_fields)
264
+
265
+ if @dynamic_field_keys.include?(attribute_name.to_sym)
266
+
267
+ field = @dynamic_fields.select { |field| field.name.to_sym == attribute_name.to_sym && field.send(self.aad_options[:fieldgroup_singular]).id == self.send(self.aad_options[:fieldgroup_singular]).id }.first
268
+
269
+ case(type)
270
+
271
+ when :writer
272
+ self.update_dynamic_attributes=(true)
273
+ self.class.send(:define_method, name) do |value|
274
+ self.send(self.aad_options[:value_singular].to_sym).send("field_#{field.id}=".to_sym, value)
275
+ end
276
+ else
277
+ self.class.send(:define_method, name) do
278
+ self.send(self.aad_options[:value_singular]).send("field_#{field.id}".to_sym, *args)
279
+ end
280
+ end
281
+ #former only set the methods now we actually have to execute them
282
+ send(name, *args)
283
+
284
+ else
285
+ # commenting out super because its throwing undefined method field_ changed in carrierwave? i think this is bad
286
+ # super
287
+ end
288
+
289
+ end
290
+
291
+ end
292
+
293
+
294
+
295
+ end
296
+
297
+ end
@@ -0,0 +1,21 @@
1
+ require "has_dynamic_fields"
2
+ require "rails"
3
+
4
+ module HasDynamicFields
5
+ class Railtie < Rails::Railtie
6
+ initializer 'has_dynamic_fields.ar_extensions' do |app|
7
+ require 'has_dynamic_fields/has_dynamic_fields' if defined?(Rails)
8
+ # ActiveRecord::Base.extend HasDynamicFields::Base
9
+ ActiveRecord::Base.send :extend, HasDynamicFields::Base
10
+ end
11
+
12
+ generators do
13
+ require "generators/dynamic_field_migration_generator"
14
+ end
15
+
16
+ rake_tasks do
17
+ load "tasks/dynamic_field_migrate.rake"
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,7 @@
1
+ require 'helper'
2
+
3
+ class TestHasDynamicFields < Test::Unit::TestCase
4
+ should "probably rename this file and start testing for real" do
5
+ flunk "hey buddy, you should probably rename this file and start testing for real"
6
+ end
7
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: has_dynamic_fields
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -138,6 +138,7 @@ files:
138
138
  - README.rdoc
139
139
  - Rakefile
140
140
  - VERSION
141
+ - has_dynamic_fields.gemspec
141
142
  - lib/generators/dynamic_field_migration_generator.rb
142
143
  - lib/generators/dynamic_field_scaffold_generator.rb
143
144
  - lib/generators/templates/add_field_migration.rb
@@ -150,9 +151,14 @@ files:
150
151
  - lib/generators/templates/fieldvalues_model_template.rb
151
152
  - lib/generators/templates/fieldvalues_table_migration.rb
152
153
  - lib/generators/templates/remove_field_migration.rb
154
+ - lib/has_dynamic_fields.rb
155
+ - lib/has_dynamic_fields/base.rb
156
+ - lib/has_dynamic_fields/has_dynamic_fields.rb
157
+ - lib/has_dynamic_fields/railtie.rb
153
158
  - lib/tasks/dynamic_field_migrate.rake
154
159
  - spec/spec_helper.rb
155
160
  - test/helper.rb
161
+ - test/test_has_dynamic_fields.rb
156
162
  homepage: http://github.com/jasonayre/has_dynamic_fields
157
163
  licenses:
158
164
  - MIT