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 +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: []
|