hstore_attribute_support 0.0.3
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/.gitignore +3 -0
- data/README +44 -0
- data/hstore_attribute_support.gemspec +16 -0
- data/lib/hstore_attribute_support/base.rb +134 -0
- data/lib/hstore_attribute_support/bootstrap.rb +17 -0
- data/lib/hstore_attribute_support/snippets.rb +142 -0
- data/lib/hstore_attribute_support.rb +7 -0
- metadata +63 -0
data/.gitignore
ADDED
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
|
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: []
|