has_dynamic_fields 0.0.1 → 0.0.2

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/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