hstore_attribute_support 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ nbproject/
2
+ .DS_Store
3
+ *.gem
data/README ADDED
@@ -0,0 +1,44 @@
1
+ == hstore_attribute_support
2
+
3
+ A class that has hstore attribute support will get the ability to set up virtual
4
+ attributes stored in hstore columns very easily.
5
+
6
+ Consider a Person class with two hstore columns named "work_details" and
7
+ "address". This class will, upon activation of the hstore attribute support, get
8
+ new class methods: work_details_hstore_accessor and address_hstore_accessor.
9
+
10
+ These methods can be used on class level (like attr_accessor) to set up getter
11
+ and setter methods for virtual attributes stored in these hstore columns.
12
+ Additionally it will allow you to give a type hint and default values along.
13
+ Because of that, new instances will be able to use an "after_initialize"
14
+ callback to set up their hstore'd attributes to the default value which was
15
+ provided, as well as enabling the getter methods to return values typecasted
16
+ (hstore data looses its type and was returned as a string otherwise...)
17
+
18
+ **Example:**
19
+
20
+ # Schema for User:
21
+ # - id: int
22
+ # - name: string
23
+ # - data: hstore
24
+ class User < ActiveRecord::Base
25
+
26
+ has_hstore_columns # activates hstore support and mixes in the new methods
27
+
28
+ # the explicit definition of a boolean attribute admin (stored in hstore
29
+ # column 'data', initialized with default value false) looks like this:
30
+ hstore_attr_accessor :admin, :boolean, false
31
+
32
+ # however for each hstore column there are autogenerated prefixed methods:
33
+ data_hstore_accessor :age, :integer # defaults to nil
34
+ data_hstore_accessor :salary, :float, 1234.56 # with default value 1234.56
35
+
36
+ # it is even possible to define a custom typecast via a lambda
37
+ data_hstore_accessor :icq, lambda{|n| n.blank? ? 'UIN: n/a' : "UIN: \##{n}"}
38
+
39
+ # as accessors get defined, validation and other stuff works out of the box:
40
+ validates :salary,
41
+ :numericality => {:greater_than => 2000},
42
+ :if => Proc.new { |u| u.admin? }
43
+
44
+ end
@@ -0,0 +1,16 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'hstore_attribute_support'
3
+ s.version = '0.0.3'
4
+ s.date = '2011-11-07'
5
+ s.summary = "Adds AR Attributes support for Postgres hstore columns "
6
+ s.description = "Adds AR Attributes support for Postgres hstore columns "
7
+ s.authors = ["Andreas Schwarzkopf"]
8
+ s.email = 'asmailbox@gmx.de'
9
+ s.homepage = 'http://rubygems.org/gems/hstore_attributes_support'
10
+ s.has_rdoc = true
11
+ s.rdoc_options = ['--charset=UTF-8']
12
+ s.files = `git ls-files`.split("\n")
13
+ s.require_paths = ["lib"]
14
+
15
+ s.add_dependency 'activerecord-postgres-hstore', '>= 0.1.2'
16
+ end
@@ -0,0 +1,134 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'active_support/concern'
4
+
5
+ module HstoreAttributeSupport
6
+
7
+ module Base
8
+
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ def self.has_hstore_columns?
13
+ true
14
+ end
15
+
16
+ class_attribute :hstore_attributes
17
+
18
+ # now we autogenerate convenient hstore_attr_accessor delegates, which can
19
+ # be used to set up this models hstore'd attributes more rails like.
20
+ # A Person class with a data hstore column could setup its name like this:
21
+ #
22
+ # data_hstore_accessor :name, :string, ''
23
+ #
24
+ # Which will set up accessors to the virtual attribute "name", defaults to
25
+ # an empty string and is stored in the data column.
26
+ # These methods are actually only syntactic sugar for the
27
+ # hstore_attr_accessor method.
28
+ self.columns.each do |column|
29
+ next unless column.type == :hstore
30
+ class_eval %Q{
31
+ def self.#{column.name}_hstore_accessor(attribute_name, cast = nil, default = nil)
32
+ return hstore_attr_accessor(attribute_name, "#{column.name}", cast, default)
33
+ end
34
+ }
35
+ end
36
+
37
+ # this will set up an after_initialize callback allowing a new model
38
+ # instance to set up default values for its hstore'd attributes.
39
+ after_initialize do
40
+ # iterate over the class instance variable "hstore_attributes", which
41
+ # contains types & defaults for virtual hstore attributes and apply them
42
+ (self.class.hstore_attributes || {}).each do |attr_name, settings|
43
+ column = settings[:column]
44
+ default = settings[:default]
45
+ hstore_data = self.send(:"#{column}") || {}
46
+ hstore_data = { attr_name.to_s => default }.merge(hstore_data)
47
+ self.send(:"#{column}=", hstore_data)
48
+ end
49
+ end
50
+ end
51
+
52
+ module ClassMethods
53
+
54
+ # this class method allows descendant classes to set up attributes, which
55
+ # are stored in a hstore column. this solves two problems:
56
+ # 1. by providing a cast hint, it will take care of the data type, which
57
+ # is lost by the hstore and was a pure string otherwise
58
+ # 2. it provides an easy way to set up defaults, as rails AR mechanism
59
+ # fail here (no database defaults available for virtual attributes)
60
+ def hstore_attr_accessor(attribute_name, hstore_column, type = nil, default = nil)
61
+ self.hstore_attributes ||= {}
62
+
63
+ self.hstore_attributes[attribute_name.to_s] = {
64
+ :column => hstore_column.to_s,
65
+ :type => type,
66
+ :default => default
67
+ }
68
+
69
+ class_eval %Q{
70
+ def #{attribute_name}
71
+ return read_hstore_attribute("#{attribute_name}")
72
+ end
73
+
74
+ def #{attribute_name}?
75
+ return read_hstore_attribute("#{attribute_name}").present?
76
+ end
77
+
78
+ def #{attribute_name}=(value)
79
+ return write_hstore_attribute("#{attribute_name}", value)
80
+ end
81
+ }
82
+ end
83
+ end
84
+
85
+ module InstanceMethods
86
+
87
+ def read_hstore_attribute(attribute_name)
88
+ ha_column = self.class.hstore_attributes[attribute_name][:column]
89
+ ha_type = self.class.hstore_attributes[attribute_name][:type]
90
+ hstore_data = self.send(:"#{ha_column}").try(:with_indifferent_access)
91
+
92
+ return nil unless hstore_data
93
+
94
+ value = hstore_data[attribute_name]
95
+
96
+ return value unless ha_type
97
+
98
+ case ha_type
99
+ when :integer
100
+ return value.to_i
101
+ when :float
102
+ return value.to_f
103
+ when :decimal
104
+ return value.to_s.to_d
105
+ when :boolean
106
+ return !(value.to_s == '' || value.to_s == 'false')
107
+ when :bool
108
+ return !(value.to_s == '' || value.to_s == 'false')
109
+ when :string
110
+ return value.to_s
111
+ when :datetime
112
+ return value.to_s.to_datetime
113
+ when :date
114
+ return value.to_s.to_date
115
+ else
116
+ end
117
+
118
+ return ha_type.call(value) if ha_type.is_a?(Proc)
119
+
120
+ raise "read_hstore_attribute failed when trying to cast the type \"#{ha_type.inspect}\". Please define the type as one of [:integer, :float, :decimal, :boolean, :string, :datetime, :date] or pass in a lambda with one parameter to perform custom casts."
121
+ end
122
+
123
+ def write_hstore_attribute(attribute_name, value)
124
+ ha_column = self.class.hstore_attributes[attribute_name][:column]
125
+ hstore_data = (self.send(:"#{ha_column}") || {}).with_indifferent_access
126
+ hstore_data = hstore_data.merge({attribute_name => value})
127
+ self.send(:"#{ha_column}=", hstore_data)
128
+ self
129
+ end
130
+
131
+ end
132
+
133
+ end
134
+ end
@@ -0,0 +1,17 @@
1
+ # encoding: UTF-8
2
+
3
+ module HstoreAttributeSupport
4
+
5
+ module Bootstrap
6
+ def has_hstore_columns?
7
+ false
8
+ end
9
+
10
+ def has_hstore_columns
11
+ unless self.include? HstoreAttributeSupport::Base
12
+ self.send :include, HstoreAttributeSupport::Base
13
+ end
14
+ end
15
+ end
16
+
17
+ end
@@ -0,0 +1,142 @@
1
+
2
+ module Something
3
+ module ClassMethods
4
+ # define an class instance variable - different for each subclass, but the
5
+ # same for each instance of these subclasses...
6
+ # its a hash containing sym.-keys representing the "virtual attribute name"
7
+ # and values which are hashes with :column, :cast, and :default values.
8
+ #
9
+ # :column defines the AR hstore column name where the attribute is stored
10
+ # :type contains a symbol defining the type or a custom proc that converts
11
+ # the value in a user defined way
12
+ # :default is the default value for this attribute
13
+ #
14
+ # An example for a person model with a "work_details" and "address" hstore
15
+ # column could look like this:
16
+ #
17
+ # hstore_attributes => {
18
+ # :employer=>{:column => work_details, :type => :string, :default => ""},
19
+ # :salary =>{:column => work_details, :type => :integer, :default => 0},
20
+ # :street =>{:column => address, :type => :string, :default => ""},
21
+ # :city =>{:column => address, :type => :string, :default => ""},
22
+ # }
23
+ #
24
+ # thus, the model has four virtual attributes which are stored in two
25
+ # differrent hstore columns.
26
+ class << self; attr_accessor :hstore_attributes; end
27
+
28
+ class_eval %Q{
29
+ # this will set up an after_initialize callback allowing a new model
30
+ # instance to set up default values for its hstore'd attributes.
31
+ after_initialize do
32
+ # iterate over the class instance variable "hstore_attributes", which
33
+ # contains types & defaults for virtual hstore attributes and apply them
34
+ (self.class.hstore_attributes || {}).each do |attr_name, settings|
35
+ column = settings[:column]
36
+ default = settings[:default]
37
+ hstore_data = self.send(:"\#{column}") || {}
38
+ hstore_data = { attr_name.to_s => default }.merge(hstore_data)
39
+ self.send(:"\#{column}=", hstore_data)
40
+ end
41
+ end
42
+ }
43
+
44
+ # now we autogenerates convenient hstore_attr_accessor delegates, which can
45
+ # be used to set up this models hstore'd attributes more rails like.
46
+ # A Person class with a data hstore column could setup its name like this:
47
+ #
48
+ # data_hstore_accessor :name, :string, ''
49
+ #
50
+ # Which will set up accessors to the virtual attribute "name", defaults to
51
+ # an empty string and is stored in the data column.
52
+ # These methods are actually only syntactic sugar for the hstore_attr_accessor
53
+ # method.
54
+ columns.each do |column|
55
+ next unless column.type == :hstore
56
+ class_eval %Q{
57
+ def self.#{column.name}_hstore_accessor(attribute_name, cast = nil, default = nil)
58
+ return hstore_attr_accessor(attribute_name, "#{column.name}", cast, default)
59
+ end
60
+ }
61
+ end
62
+
63
+ # this class method allows descendant classes to set up attributes, which are
64
+ # stored in a hstore column. this solves two problems:
65
+ # 1. by providing a cast hint, it will take care of the data type, which is
66
+ # lost by the hstore and was a pure string otherwise
67
+ # 2. it provides an easy way of setting up defaults, as rails AR mechanisms
68
+ # fail here (no database defaults available for virtual attributes)
69
+ def self.hstore_attr_accessor(attribute_name, hstore_column, type = nil, default = nil)
70
+ self.hstore_attributes ||= {}
71
+
72
+ self.hstore_attributes[attribute_name.to_s] = {
73
+ :column => hstore_column.to_s,
74
+ :type => type,
75
+ :default => default
76
+ }
77
+
78
+ class_eval %Q{
79
+ def #{attribute_name}
80
+ return read_hstore_attribute("#{attribute_name}")
81
+ end
82
+
83
+ def #{attribute_name}?
84
+ return read_hstore_attribute("#{attribute_name}").present?
85
+ end
86
+
87
+ def #{attribute_name}=(value)
88
+ return write_hstore_attribute("#{attribute_name}", value)
89
+ end
90
+ }
91
+ end
92
+
93
+ private
94
+
95
+ def read_hstore_attribute(attribute_name)
96
+ ha_column = self.class.hstore_attributes[attribute_name][:column]
97
+ ha_type = self.class.hstore_attributes[attribute_name][:type]
98
+ hstore_data = self.send(:"#{ha_column}").try(:with_indifferent_access)
99
+
100
+ return nil unless hstore_data
101
+
102
+ value = hstore_data[attribute_name]
103
+
104
+ return value unless ha_type
105
+
106
+ case ha_type
107
+ when :integer
108
+ return value.to_i
109
+ when :float
110
+ return value.to_f
111
+ when :decimal
112
+ return value.to_s.to_d
113
+ when :boolean
114
+ return !(value.to_s == '' || value.to_s == 'false')
115
+ when :bool
116
+ return !(value.to_s == '' || value.to_s == 'false')
117
+ when :string
118
+ return value.to_s
119
+ when :datetime
120
+ return value.to_s.to_datetime
121
+ when :date
122
+ return value.to_s.to_date
123
+ else
124
+ end
125
+
126
+ return ha_type.call(value) if ha_type.is_a?(Proc)
127
+
128
+ raise "read_hstore_attribute failed when trying to cast the type \"#{ha_type.inspect}\". Please define the type as one of [:integer, :float, :decimal, :boolean, :string, :datetime, :date] or pass in a lambda with one parameter to perform custom casts."
129
+ end
130
+
131
+ def write_hstore_attribute(attribute_name, value)
132
+ ha_column = self.class.hstore_attributes[attribute_name][:column]
133
+ hstore_data = (self.send(:"#{ha_column}") || {}).with_indifferent_access
134
+ hstore_data = hstore_data.merge({attribute_name => value})
135
+ self.send(:"#{ha_column}=", hstore_data)
136
+ end
137
+
138
+ end
139
+
140
+ module InstanceMethods
141
+ end
142
+ end
@@ -0,0 +1,7 @@
1
+ require "hstore_attribute_support/bootstrap"
2
+ require "hstore_attribute_support/base"
3
+
4
+ # Attach ourselves to ActiveRecord
5
+ if defined?(ActiveRecord::Base)
6
+ ActiveRecord::Base.extend HstoreAttributeSupport::Bootstrap
7
+ end
metadata ADDED
@@ -0,0 +1,63 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hstore_attribute_support
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Andreas Schwarzkopf
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-11-07 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activerecord-postgres-hstore
16
+ requirement: &70277821577800 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 0.1.2
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70277821577800
25
+ description: ! 'Adds AR Attributes support for Postgres hstore columns '
26
+ email: asmailbox@gmx.de
27
+ executables: []
28
+ extensions: []
29
+ extra_rdoc_files: []
30
+ files:
31
+ - .gitignore
32
+ - README
33
+ - hstore_attribute_support.gemspec
34
+ - lib/hstore_attribute_support.rb
35
+ - lib/hstore_attribute_support/base.rb
36
+ - lib/hstore_attribute_support/bootstrap.rb
37
+ - lib/hstore_attribute_support/snippets.rb
38
+ homepage: http://rubygems.org/gems/hstore_attributes_support
39
+ licenses: []
40
+ post_install_message:
41
+ rdoc_options:
42
+ - --charset=UTF-8
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ none: false
47
+ requirements:
48
+ - - ! '>='
49
+ - !ruby/object:Gem::Version
50
+ version: '0'
51
+ required_rubygems_version: !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ! '>='
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ requirements: []
58
+ rubyforge_project:
59
+ rubygems_version: 1.8.11
60
+ signing_key:
61
+ specification_version: 3
62
+ summary: Adds AR Attributes support for Postgres hstore columns
63
+ test_files: []