flexi_model 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.
@@ -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: []