flexi_model 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,290 @@
1
+ # encoding: UTF-8
2
+ module FlexiModel
3
+ module Fields
4
+ TYPES = [:integer, :boolean, :multiple, :decimal, :float, :string, :text,
5
+ :datetime, :date, :time, :email, :phone, :address, :location, :attachment]
6
+
7
+ extend ActiveSupport::Concern
8
+
9
+ MULTI_PARAMS_FIELDS = ['datetime', 'date', 'time']
10
+
11
+ included do
12
+ class_eval <<-CODE, __FILE__, __LINE__ + 1
13
+ attr_accessor :attributes
14
+
15
+ @@flexi_visible = true
16
+ cattr_accessor :flexi_visible
17
+
18
+ @@flexi_fields = []
19
+ cattr_accessor :flexi_fields
20
+
21
+ @@none_flexi_fields = []
22
+ cattr_accessor :none_flexi_fields
23
+
24
+ @@_flexi_fields_by_name = {}
25
+ cattr_accessor :_flexi_fields_by_name
26
+
27
+ @@flexi_namespace = nil
28
+ cattr_accessor :flexi_namespace
29
+
30
+ @@flexi_partition_id = 0
31
+ cattr_accessor :flexi_partition_id
32
+
33
+ @@flexi_name_field = :name
34
+ cattr_accessor :flexi_name_field
35
+
36
+ CODE
37
+ end
38
+
39
+ # Define attribute initialization constructor
40
+ def initialize(args = { })
41
+ @attributes = { }
42
+ assign_attributes(args) if args.present?
43
+ end
44
+
45
+ def assign_attributes(args)
46
+ _all_keys = args.keys
47
+ _multi_params_keys = _all_keys.select { |k| k.to_s.match(/\([\w]+\)/) }
48
+ _normal_keys = _all_keys - _multi_params_keys
49
+
50
+ # Execute only single parameter accessors
51
+ _normal_keys.each do |k|
52
+ self.send :"#{k.to_s}=", args[k]
53
+ end
54
+
55
+ # Execute multi params accessors
56
+ if _multi_params_keys.present?
57
+ _groups = { }
58
+ _multi_params_keys.each do |k|
59
+ _accessor = k.to_s.gsub(/\([\w]+\)/, '')
60
+ _index = k.to_s.match(/\(([\w]+)\)/)[1].to_i - 1
61
+
62
+ _groups[_accessor] ||= []
63
+ _groups[_accessor].insert(_index, args[k])
64
+ end
65
+
66
+ _groups.each do |k, values|
67
+ _int_values = values.map(&:to_i)
68
+ _field = self.flexi_fields.select { |f| f.name.to_s == k }.first
69
+
70
+ if _field
71
+ _value = case _field.type
72
+ when 'datetime'
73
+ DateTime.new(*_int_values)
74
+ when 'time'
75
+ Time.new(*([Time.now.year, Time.now.month, Time.now.day] + _int_values))
76
+ when 'date'
77
+ Date.new(*_int_values)
78
+ end
79
+
80
+ self.send :"#{k}=", _value
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ # Return default name from the object instance
87
+ # This accessor could be set through set_flexi_name_field :title
88
+ def _name
89
+ if self.respond_to?(self.flexi_name_field)
90
+ self.send(self.flexi_name_field)
91
+ else
92
+ raise "Define your name field accessor (which may return the object
93
+ title, name or whatever it describes about the current instance)
94
+ - set_flexi_name_field :different_accessor"
95
+ end
96
+ end
97
+
98
+ def to_s
99
+ %{#<#{self.class.name}:#{sprintf '0x%x', self.object_id} #{@attributes.map { |k, v| ":#{k}=>'#{v}'" }.join(' ')}>}
100
+ end
101
+
102
+ module ClassMethods
103
+
104
+ # Set field name to determine default name field.
105
+ def set_flexi_name_field(field)
106
+ self.flexi_name_field = field
107
+ end
108
+
109
+ # Set current model as visible or invisible to admin panel
110
+ # Setting false won't publicize this model over admin panel
111
+ def set_flexi_visible(bool)
112
+ self.flexi_visible = bool
113
+ end
114
+
115
+ # Isolate and group all models under a single administration panel
116
+ # This is useful when we are hosting these models under a single platform.
117
+ def set_flexi_partition_id(_id)
118
+ self.flexi_partition_id = _id
119
+ end
120
+
121
+ # Set filed with name, type and default value or proc
122
+ #
123
+ # name - Field name should be downcased multi words separated by underscore
124
+ # type - Supported type String, Integer, Time, Date
125
+ # options - Hash of options
126
+ # default - Default value proc or object
127
+ # singular - Singular label
128
+ # plural - Plural label
129
+ #
130
+ # Return +Field+ instance
131
+ def flexi_field(name, type, options = { })
132
+ accessible = options.delete(:accessible)
133
+ default = options.delete(:default)
134
+ singular = options.delete(:singular) || name.to_s.singularize
135
+ plural = options.delete(:plural) || name.to_s.pluralize
136
+ _type = type.is_a?(Symbol) || type.is_a?(String) ? type : type.name
137
+
138
+
139
+ field = FlexiField.new(name, _type.to_s.downcase, default)
140
+ field.accessible = accessible
141
+ field.singular = singular
142
+ field.plural = plural
143
+ field.options = options
144
+
145
+ opt_accessors = options.delete(:accessors)
146
+ generate_accessors = opt_accessors.nil? ? true : opt_accessors
147
+
148
+ define_field(field, generate_accessors)
149
+ end
150
+
151
+ alias_method :_ff, :flexi_field
152
+
153
+ # Define flexible field definition
154
+ (TYPES - [:attachment]).each do |_field|
155
+ self.class_eval <<-CODE
156
+ def _#{_field.to_s}(*args)
157
+ options = args.last && args.last.is_a?(Hash) ? args.last : {}
158
+ args.each do |_field_name|
159
+ unless _field_name.is_a?(Hash)
160
+ flexi_field _field_name, :#{_field.to_s}, options
161
+ end
162
+ end
163
+ end
164
+ CODE
165
+ end
166
+
167
+ # Remove field by field name
168
+ # Return list of existing fields
169
+ def remove_flexi_field(name)
170
+ flexi_fields.reject! { |f| f.name == name }
171
+ end
172
+
173
+ def define_field(field, generate_accessors = true)
174
+ # Add new field
175
+ if generate_accessors
176
+ # Remove existing definition
177
+ remove_flexi_field field.name
178
+
179
+ flexi_fields << field
180
+
181
+ # Map name and field
182
+ _flexi_fields_by_name[
183
+ (field.name.is_a?(Symbol) ?
184
+ field.name : field.name.to_sym)] = field
185
+
186
+ _define_accessors(field)
187
+ else
188
+ none_flexi_fields << field
189
+ end
190
+ end
191
+
192
+ private
193
+ def _define_accessors(field)
194
+ # Define setter & getter
195
+ self.class_eval <<-CODE, __FILE__, __LINE__ + 1
196
+ def #{field.name.to_s}=(v)
197
+ @attributes[:'#{field.name.to_s}'] = _cast(v, :#{field.type})
198
+ end
199
+
200
+ def #{field.name.to_s}
201
+ @attributes[:'#{field.name.to_s}'] ||= _cast(_get_value(:'#{field.name.to_s}'), :#{field.type})
202
+ end
203
+
204
+ CODE
205
+
206
+ # Define ? method if boolean field
207
+ if field.type == 'boolean'
208
+ self.class_eval <<-CODE
209
+ def #{field.name.to_s}?
210
+ self.#{field.name.to_s}
211
+ end
212
+ CODE
213
+ end
214
+ end
215
+ end
216
+
217
+ def _cast(_value, _type)
218
+ return _value if _value.nil?
219
+
220
+ case _type
221
+ when :decimal, :float
222
+ _value.to_f
223
+ when :integer, :number
224
+ _value.to_i
225
+ when :multiple
226
+ if _value.is_a?(String)
227
+ YAML.load(_value)
228
+ elsif Array
229
+ _value.map do |v|
230
+ if v.is_a?(Hash)
231
+ if v.values.map(&:blank?).uniq == [true]
232
+ nil
233
+ else
234
+ v
235
+ end
236
+ else
237
+ v
238
+ end
239
+ end.compact
240
+ end
241
+ else
242
+ _value
243
+ end
244
+ end
245
+
246
+ def _get_value(field_name)
247
+ return _load_value_from_record(field_name) if self._record.present?
248
+
249
+ _default_value(field_name)
250
+ end
251
+
252
+ def _load_value_from_record(field_name)
253
+ val = self._record.value_of(field_name.to_s)
254
+ if val.present?
255
+ val.value
256
+ else
257
+ nil
258
+ end
259
+ end
260
+
261
+ def _default_value(field_name)
262
+ _field = _flexi_fields_by_name[field_name]
263
+ if _field.default.present?
264
+ _field.value(self)
265
+ else
266
+ nil
267
+ end
268
+ end
269
+
270
+
271
+ class FlexiField
272
+ attr_accessor :name, :type, :default, :singular, :plural, :accessible, :options
273
+
274
+ def initialize(name, type, default = nil)
275
+ @name = name
276
+ @type = type
277
+ @default = default
278
+ @options = { }
279
+ end
280
+
281
+ def value(context)
282
+ if default.is_a?(Proc)
283
+ default.call(context)
284
+ else
285
+ default
286
+ end
287
+ end
288
+ end
289
+ end
290
+ end
@@ -0,0 +1,61 @@
1
+ module FlexiModel
2
+ module Filter
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ class_eval <<-CODE
7
+ @@flexi_filters = {}
8
+ cattr_accessor :flexi_filters
9
+ CODE
10
+ end
11
+
12
+ module ClassMethods
13
+ # Define filter for scoping data
14
+ #
15
+ # name - Set filter name
16
+ # options - Hash of options
17
+ # field_types - Define renderable field types
18
+ # field_values - Define initial field values procs
19
+ # proc - Set lambda for the specific filter
20
+ #
21
+ # Example -
22
+ #
23
+ # class Category
24
+ # include FlexiModel
25
+ # filter :by_name, {}, lambda { |param1, param2| ... }
26
+ # end
27
+ #
28
+ def flexi_filter(name, options = {}, &proc)
29
+ raise "No lambda is defined for scope :#{name}" if proc.nil?
30
+
31
+ accepted_params = proc.parameters.map(&:last)
32
+
33
+ self.flexi_filters[name.to_sym] = {
34
+ param_keys: accepted_params,
35
+ options: options,
36
+ proc: proc
37
+ }
38
+
39
+ _define_filter_method(name)
40
+ end
41
+
42
+ def _define_filter_method(name)
43
+ self.class_eval <<-CODE, __FILE__, __LINE__ + 1
44
+ def self.#{name.to_s}(args = {})
45
+ _filter_params = self.flexi_filters[:#{name.to_s}]
46
+ _values = _filter_params[:param_keys].map{ |k| args[k] }
47
+
48
+ _inst = args[:instance] || self
49
+ _block = _filter_params[:proc]
50
+ _inst.instance_exec(*_values, &_block)
51
+ end
52
+
53
+ def #{name.to_s}(args = {})
54
+ args[:instance] = self
55
+ self.class.#{name.to_s}(args)
56
+ end
57
+ CODE
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,24 @@
1
+ module FlexiModel
2
+ module Persistence
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def destroy_all; end
7
+ def delete_all; end
8
+
9
+ def count; 0 end
10
+ def length; 0 end
11
+ end
12
+
13
+ # Persist data in AR backend or Mongoid backend
14
+ def save; end
15
+ def create; end
16
+ def update_attributes; end
17
+ def update_attribute; end
18
+ def destroy; end
19
+ def destroy_all; end
20
+ def delete_all; end
21
+ def count; end
22
+ def length; end
23
+ end
24
+ end
@@ -0,0 +1,37 @@
1
+ module FlexiModel
2
+ module Validations
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ if defined?(ActiveModel)
7
+ include ActiveModel
8
+ include ActiveModel::Validations
9
+ end
10
+ end
11
+ end
12
+
13
+ def create(options = { })
14
+ if perform_validation(options)
15
+ super
16
+ else
17
+ self
18
+ end
19
+ end
20
+
21
+ def update(options = { })
22
+ if perform_validation(options)
23
+ super
24
+ else
25
+ self
26
+ end
27
+ end
28
+
29
+ def perform_validation(options = { })
30
+ if options[:validate].nil? || options[:validate]
31
+ valid?
32
+ else
33
+ true
34
+ end
35
+ end
36
+
37
+ end
@@ -0,0 +1,38 @@
1
+ require 'flexi_model/fields'
2
+ require 'flexi_model/callbacks'
3
+ require 'flexi_model/validations'
4
+ require 'flexi_model/association'
5
+ require 'flexi_model/filter'
6
+ require 'flexi_model/attachment_field'
7
+
8
+ module FlexiModel
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ class_eval <<-RUBY
13
+ attr_accessor :_id, :_record
14
+ alias_method :id, :_id
15
+ RUBY
16
+ end
17
+
18
+ include FlexiModel::Fields
19
+ include FlexiModel::Callbacks
20
+ include FlexiModel::Validations
21
+
22
+ if defined?(ActiveRecord)
23
+ require 'flexi_model/ar_models'
24
+ require 'flexi_model/ar_persistence'
25
+ require 'flexi_model/ar_queryable'
26
+
27
+ include FlexiModel::ArModels
28
+ include FlexiModel::ArPersistence
29
+ include FlexiModel::ArQueryable
30
+ else
31
+ raise "No Active Record Found"
32
+ end
33
+
34
+ include FlexiModel::Association
35
+ include FlexiModel::Filter
36
+ include FlexiModel::AttachmentField
37
+
38
+ end
@@ -0,0 +1,20 @@
1
+ module FlexiModel
2
+ module Generators
3
+ class InstallGenerator < Rails::Generators::Base
4
+ include Rails::Generators::Migration
5
+ source_root File.expand_path('../templates', __FILE__)
6
+
7
+ # Implement the required interface for Rails::Generators::Migration.
8
+ def self.next_migration_number(dirname) #:nodoc:
9
+ next_migration_number = current_migration_number(dirname) + 1
10
+ ActiveRecord::Migration.next_migration_number(next_migration_number)
11
+ end
12
+
13
+ def generate_install
14
+ Dir.glob(File.join(File.dirname(__FILE__), 'templates/*.rb')).each do |file|
15
+ migration_template file, "db/migrate/#{file.split('/').last}"
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,18 @@
1
+ class CreateFlexiModelCollections < ActiveRecord::Migration
2
+ def change
3
+ create_table "flexi_model_collections" do |t|
4
+ t.string 'name'
5
+ t.string 'singular_label'
6
+ t.string 'plural_label'
7
+ t.string 'namespace'
8
+ t.integer 'partition_id'
9
+
10
+ t.datetime "created_at"
11
+ t.datetime "updated_at"
12
+ end
13
+
14
+ add_index "flexi_model_collections", [:namespace, :name]
15
+ add_index "flexi_model_collections", [:namespace, :name, :partition_id],
16
+ name: 'index_ns_name_pi', unique: true
17
+ end
18
+ end
@@ -0,0 +1,11 @@
1
+ class CreateFlexiModelCollectionsFields < ActiveRecord::Migration
2
+ def change
3
+ create_table 'flexi_model_collections_fields', :id => false, :force => true do |t|
4
+ t.integer 'collection_id'
5
+ t.integer 'field_id'
6
+ end
7
+
8
+ add_index 'flexi_model_collections_fields', [:collection_id]
9
+ add_index 'flexi_model_collections_fields', [:field_id]
10
+ end
11
+ end
@@ -0,0 +1,20 @@
1
+ class CreateFlexiModelFields < ActiveRecord::Migration
2
+ def change
3
+ create_table 'flexi_model_fields', :force => true do |t|
4
+ t.string 'name'
5
+ t.string 'singular_label'
6
+ t.string 'plural_label'
7
+ t.string 'namespace'
8
+ t.integer 'partition_id'
9
+
10
+ t.string 'field_type'
11
+ t.text 'default_value'
12
+ end
13
+
14
+ add_index 'flexi_model_fields', [:namespace, :name]
15
+ add_index 'flexi_model_fields', [:namespace, :name, :field_type]
16
+ add_index 'flexi_model_fields', [:namespace, :name, :field_type, :partition_id],
17
+ :unique => true,
18
+ name: 'index_ns_name_ft_pi'
19
+ end
20
+ end
@@ -0,0 +1,12 @@
1
+ class CreateFlexiModelRecords < ActiveRecord::Migration
2
+ def change
3
+ create_table 'flexi_model_records', :force => true do |t|
4
+ t.integer 'collection_id'
5
+ t.string 'namespace'
6
+ t.datetime "created_at"
7
+ t.datetime "updated_at"
8
+ end
9
+
10
+ add_index 'flexi_model_records', [:namespace, :collection_id]
11
+ end
12
+ end
@@ -0,0 +1,17 @@
1
+ class CreateFlexiModelValues < ActiveRecord::Migration
2
+ def change
3
+ create_table 'flexi_model_values', :force => true do |t|
4
+ t.integer 'record_id'
5
+ t.integer 'field_id'
6
+ t.boolean 'bool_value'
7
+ t.integer 'int_value'
8
+ t.decimal 'dec_value'
9
+ t.string 'str_value'
10
+ t.text 'txt_value'
11
+ t.datetime 'dt_value'
12
+ end
13
+
14
+ add_index 'flexi_model_values', [:record_id, :field_id]
15
+
16
+ end
17
+ end
metadata ADDED
@@ -0,0 +1,92 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: flexi_model
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - nhm tanveer hossain khan
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-07-10 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activesupport
16
+ requirement: &2153893780 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 3.2.6
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *2153893780
25
+ - !ruby/object:Gem::Dependency
26
+ name: jeweler
27
+ requirement: &2153892920 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *2153892920
36
+ description: Flexible Model
37
+ email:
38
+ - hasan83bd@gmail.com
39
+ executables: []
40
+ extensions: []
41
+ extra_rdoc_files:
42
+ - README.md
43
+ files:
44
+ - lib/discover.rb
45
+ - lib/flexi_model.rb
46
+ - lib/flexi_model/ar_models.rb
47
+ - lib/flexi_model/ar_models/collection.rb
48
+ - lib/flexi_model/ar_models/field.rb
49
+ - lib/flexi_model/ar_models/record.rb
50
+ - lib/flexi_model/ar_models/value.rb
51
+ - lib/flexi_model/ar_persistence.rb
52
+ - lib/flexi_model/ar_queryable.rb
53
+ - lib/flexi_model/association.rb
54
+ - lib/flexi_model/attachment_field.rb
55
+ - lib/flexi_model/callbacks.rb
56
+ - lib/flexi_model/fields.rb
57
+ - lib/flexi_model/filter.rb
58
+ - lib/flexi_model/stub_persistence.rb
59
+ - lib/flexi_model/validations.rb
60
+ - lib/generators/flexi_model/install/install_generator.rb
61
+ - lib/generators/flexi_model/install/templates/create_flexi_model_collections.rb
62
+ - lib/generators/flexi_model/install/templates/create_flexi_model_collections_fields.rb
63
+ - lib/generators/flexi_model/install/templates/create_flexi_model_fields.rb
64
+ - lib/generators/flexi_model/install/templates/create_flexi_model_records.rb
65
+ - lib/generators/flexi_model/install/templates/create_flexi_model_values.rb
66
+ - README.md
67
+ homepage: https://github.com/we4tech/flexi-model/
68
+ licenses: []
69
+ post_install_message:
70
+ rdoc_options: []
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
+ none: false
81
+ requirements:
82
+ - - ! '>='
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ requirements: []
86
+ rubyforge_project:
87
+ rubygems_version: 1.8.10
88
+ signing_key:
89
+ specification_version: 3
90
+ summary: Build flexible database model with dynamic fields (right now based on ActiveRecord
91
+ soon it will work with mongoid too)
92
+ test_files: []