settings_on_rails 0.1.30 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1902e352d4b86e9b07fb40e0b3364a32ebf810f2
4
- data.tar.gz: 0ff9fa4d7d46470da596aab09983f07415b58980
3
+ metadata.gz: fbb69ae794bd085f9996cb04e3246afec69c2d50
4
+ data.tar.gz: 1994e3ead98a2075bdd71d0b406ffcd9a98e3487
5
5
  SHA512:
6
- metadata.gz: a23b884b80b9b6fc85920e42cc5b368a680176bd78e8e6910570ec6cf1b907bb39d42bc17f16523f99024a4994689f47b5b878a4a3a14bae5e13247543f700f8
7
- data.tar.gz: 49efc61fc8d8f6efabdcfaee240db7f47032084c622fbf75ba79d4899385f6944326cf4de84a1a66d8cfa7d5c68caf77c7846e2555df543647498ee5fecc7a90
6
+ metadata.gz: 5cfd24abffdbc9c209d7e921e19f33e0170b10fafa25682546119402c7c0897a6706388fc0172b029dce06a4504827eb2f7d9de3c69ba675deda69e2e2d787ee
7
+ data.tar.gz: cc40d8e46d5ed04e2757ce10f6b5c58d33d099bce48f9680a03dd3b578e8f83108b0c95774cfb290568f8a52856b3bda09dc8b6f6668c9b86e31def8bc121ac7
data/Gemfile CHANGED
@@ -4,4 +4,4 @@ source 'https://rubygems.org'
4
4
  gemspec
5
5
 
6
6
  gem 'coveralls', require: false
7
-
7
+ gem 'byebug'
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # SettingsOnRails
1
+ # Settings on Rails
2
2
  [![Build Status](https://travis-ci.org/allenwq/settings_on_rails.svg?branch=master)](https://travis-ci.org/allenwq/settings_on_rails)
3
3
  [![Coverage Status](https://coveralls.io/repos/allenwq/settings_on_rails/badge.svg?branch=master)](https://coveralls.io/r/allenwq/settings_on_rails?branch=master)
4
4
 
@@ -46,7 +46,6 @@ Get settings
46
46
  @blog.settings(:theme).background_color # returns 'blue'
47
47
 
48
48
  @blog.settings(:post).pagination # returns nil if not set
49
-
50
49
  ```
51
50
 
52
51
  ## Default Values
@@ -56,7 +55,8 @@ class Blog < ActiveRecord::Base
56
55
  has_settings_on :column
57
56
 
58
57
  has_settings do |s|
59
- s.has_key :theme, defaults:{ background_color: 'red', text_size: 50 }
58
+ s.key :theme, defaults:{ background_color: 'red', text_size: 50 }
59
+ s.attr :title, default: 'My Space'
60
60
  end
61
61
  end
62
62
  ```
@@ -64,24 +64,38 @@ OR
64
64
  ```ruby
65
65
  class Blog < ActiveRecord::Base
66
66
  has_settings_on :column do |s|
67
- s.define :theme, defaults:{ background_color: 'red', text_size: 50 }
67
+ s.key :theme, defaults:{ background_color: 'red', text_size: 50 }
68
+ s.attr :title, default: 'My Space'
68
69
  end
69
70
  end
70
71
  ```
71
72
 
72
- ## Nested/Multiple Keys
73
-
73
+ You can get these defaults by:
74
+ ```ruby
75
+ @blog.settings(:theme).background_color # 'red'
76
+ @blog.settings(:theme).text_size # 50
77
+ @blog.settings.title # 'My Space'
78
+ ```
74
79
 
80
+ ## Nested Keys
81
+ Settings on Rails supports nested keys
75
82
  ```ruby
76
- @blog.settings(:theme, :homepage).background_color = 'white'
77
- #OR
83
+ # Set
78
84
  @blog.settings(:theme).settings(:homepage).background_color = 'white'
85
+ # Get
86
+ @blog.settings(:theme).settings(:homepage).background_color # 'white'
87
+ ```
79
88
 
89
+ # Multiple Keys
90
+ You can also define you nested keys in following ways, it's equal to nested keys
91
+ ```ruby
92
+ # Set
93
+ @blog.settings(:theme, :homepage).background_color = 'white'
94
+ # Get
80
95
  @blog.settings(:theme, :homepage).background_color # 'white'
81
- #OR
82
- @blog.settings(:theme).settings(:homepage).background_color # 'white'
83
96
  ```
84
97
 
98
+
85
99
  ## Method Name Customization
86
100
  You can customize the name of the settings method
87
101
  ```ruby
@@ -3,20 +3,24 @@ module SettingsOnRails
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  module ClassMethods
6
- def has_settings_on(column, options = {})
7
- SettingsColumn.setup(self, column)
6
+ def has_settings_on(column, options = {}, &block)
7
+ Configuration.init(self, column)
8
8
 
9
9
  method_name = options[:method] || :settings
10
10
  define_method method_name do |*keys|
11
- column = SettingsColumn.check!(self)
11
+ column = Configuration.check!(self)
12
12
 
13
- SettingsHandler.new(keys, self, column, method_name)
13
+ Settings.new(keys, self, column, method_name)
14
14
  end
15
+
16
+ has_settings(&block)
15
17
  end
16
18
 
17
- def has_settings
18
- raise NoSettingsColumnError unless SettingsColumn::NAME
19
- yield if block_given?
19
+ def has_settings(*keys)
20
+ settings = HasSettings.new(keys, self, Configuration::DEFAULTS_COLUMN)
21
+ yield settings if block_given?
22
+
23
+ settings
20
24
  end
21
25
  end
22
26
  end
@@ -0,0 +1,54 @@
1
+ module SettingsOnRails
2
+ module Configuration
3
+ NAME_COLUMN = :settings_column_name
4
+ DEFAULTS_COLUMN = :default_settings
5
+
6
+ # Initialize needed variables for given class
7
+ # @param [ActiveRecord] klass, the Model who has settings
8
+ # @param [Symbol] column, the column to store settings
9
+ def self.init(klass, column)
10
+ raise ArgumentError.new("has_settings_on: symbol expected, but got a #{column.class}") unless column.is_a?(Symbol)
11
+
12
+ klass.class_eval do
13
+ class_attribute Configuration::NAME_COLUMN, Configuration::DEFAULTS_COLUMN
14
+
15
+ serialize column, Hash
16
+ Configuration::init_defaults_column(self)
17
+ Configuration::init_name_column(self, column)
18
+ end
19
+ end
20
+
21
+ # Returns the name of settings column for that instance
22
+ def self.column_name(instance)
23
+ instance.class.send(Configuration::NAME_COLUMN)
24
+ end
25
+
26
+ # Check for the validity of the settings column
27
+ # Returns the column name if valid
28
+ def self.check!(instance)
29
+ settings_column_name = column_name(instance)
30
+ raise NoSettingsColumnError unless settings_column_name
31
+ raise ColumnNotExistError unless instance.has_attribute?(settings_column_name)
32
+ raise InvalidColumnTypeError if column_type_not_text?(instance, settings_column_name)
33
+
34
+ settings_column_name
35
+ end
36
+
37
+ # init to Hash {} for data attribute in klass if nil
38
+ def self.init_defaults_column(klass)
39
+ defaults = klass.send(Configuration::DEFAULTS_COLUMN)
40
+ klass.send(Configuration::DEFAULTS_COLUMN.to_s + '=', {}) unless defaults
41
+ end
42
+
43
+ def self.init_name_column(klass, column_name)
44
+ klass.send(Configuration::NAME_COLUMN.to_s + '=', column_name)
45
+ end
46
+
47
+
48
+ private
49
+
50
+ def self.column_type_not_text?(instance, settings_column)
51
+ return true if instance.column_for_attribute(settings_column).try(:sql_type) != 'text'
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,68 @@
1
+ require 'settings_on_rails/key_tree_builder'
2
+
3
+ module SettingsOnRails
4
+ # Creates a object to save default settings
5
+ #
6
+ # @param [Array] keys, the symbol keys in a array
7
+ # @param [Class] target_model, default values will be saved in an class attribute in target model
8
+ # @param [Symbol] column_name, the column name in target_model to save default values
9
+ # @param [HasSettings] parent, parent has_settings object
10
+ class HasSettings < KeyTreeBuilder
11
+ def initialize(keys, target_model, column_name, parent = nil)
12
+ super(keys, target_model, column_name, parent)
13
+
14
+ @target_model = target_model
15
+ @column_name = column_name
16
+ end
17
+
18
+ REGEX_ATTR = /\A([a-z]\w*)\Z/i
19
+
20
+ # Declare a key, with default values
21
+ #
22
+ # @param [Symbol] keys
23
+ # @param [Hash] options, the last param must be an option with a defaults hash
24
+ def key(*keys)
25
+ options = keys.extract_options!
26
+ raise ArgumentError.new("has_settings: Option :defaults expected, but got #{options.keys.join(', ')}") unless options.blank? || (options.keys == [:defaults])
27
+ keys.each do |key_name|
28
+ raise ArgumentError.new("has_settings: symbol expected, but got a #{key_name.class}") unless key_name.is_a?(Symbol)
29
+ end
30
+
31
+ options[:defaults].each do |k, v|
32
+ has_settings(*keys).attr(k, default: v)
33
+ end
34
+ end
35
+
36
+ # Declare an attribute with default value
37
+ #
38
+ # @param [Symbol] value
39
+ # @param [Hash] options, options with a default Hash
40
+ def attr(value, options = {})
41
+ raise ArgumentError.new("has_settings: symbol expected, but got a #{value.class}") unless value.is_a?(Symbol)
42
+ raise ArgumentError.new("has_settings: Option :default expected, but got #{options.keys.join(', ')}") unless options.blank? || (options.keys == [:default])
43
+
44
+ default_value = options[:default]
45
+ raise 'Error' unless value.to_s =~ REGEX_ATTR
46
+
47
+ _set_value(value.to_s, default_value)
48
+ end
49
+
50
+ def has_settings(*keys)
51
+ settings = HasSettings.new(keys, @target_model, @column_name, self)
52
+ yield settings if block_given?
53
+ settings
54
+ end
55
+
56
+ private
57
+
58
+ def _set_value(name, v)
59
+ build_nodes
60
+
61
+ if v.nil?
62
+ current_node.delete(name)
63
+ else
64
+ current_node[name] = v
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,68 @@
1
+ module SettingsOnRails
2
+ class KeyTreeBuilder
3
+ attr_accessor :keys, :parent
4
+
5
+ # All keys must be symbols, and attributes are strings
6
+ # Thus we can differentiate settings(:key).attr and settings(:key, :attr)
7
+ def initialize(keys, target_obj, target_column_name, parent)
8
+ keys.each do |key_name|
9
+ raise ArgumentError.new("has_settings: symbol expected, but got a #{key_name.class}") unless key_name.is_a?(Symbol)
10
+ end
11
+ @keys = keys
12
+ @target_obj = target_obj
13
+ @column_name = target_column_name
14
+ @parent = parent
15
+ end
16
+
17
+ # Returns column[key_chain[0]][key_chain[1]][...]
18
+ def current_node
19
+ return nil unless _key_node_exist?
20
+
21
+ _key_chain.inject(_target_column) { |h, key| h[key] }
22
+ end
23
+
24
+ # Call this method before set any values
25
+ def build_nodes
26
+ value = _target_column
27
+
28
+ for key in _key_chain
29
+ value[key] = {} unless value[key]
30
+ value = value[key]
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def _key_node_exist?
37
+ value = _target_column
38
+
39
+ for key in _key_chain
40
+ value = value[key]
41
+ return false unless value
42
+ end
43
+
44
+ true
45
+ end
46
+
47
+ # Returns a key chain which includes all parent's keys and self keys
48
+ def _key_chain
49
+ handler = self
50
+ key_chain = []
51
+
52
+ begin
53
+ key_chain = handler.keys + key_chain
54
+ handler = handler.parent
55
+ end while handler
56
+
57
+ key_chain
58
+ end
59
+
60
+ def _target_column
61
+ if @target_obj.respond_to?(:read_attribute)
62
+ @target_obj.read_attribute(@column_name.to_sym)
63
+ else
64
+ @target_obj.send(@column_name.to_sym)
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,65 @@
1
+ require 'settings_on_rails/key_tree_builder'
2
+
3
+ module SettingsOnRails
4
+ class Settings < KeyTreeBuilder
5
+ def initialize(keys, target_object, settings_column_name, method_name, parent = nil)
6
+ super(keys, target_object, settings_column_name, parent)
7
+ @target_object = target_object
8
+ @column_name = settings_column_name
9
+ @method_name = method_name
10
+
11
+ self.class_eval do
12
+ define_method(method_name, instance_method(:_settings))
13
+ end
14
+ end
15
+
16
+ REGEX_SETTER = /\A([a-z]\w*)=\Z/i
17
+ REGEX_GETTER = /\A([a-z]\w*)\Z/i
18
+
19
+ def respond_to?(method_name, include_priv=false)
20
+ super || method_name.to_s =~ REGEX_SETTER
21
+ end
22
+
23
+ def method_missing(method_name, *args, &block)
24
+ if method_name.to_s =~ REGEX_SETTER && args.size == 1
25
+ _set_value($1, args.first)
26
+ elsif method_name.to_s =~ REGEX_GETTER && args.size == 0
27
+ _get_value($1) || _default_settings($1)
28
+ else
29
+ super
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def _settings(*keys)
36
+ raise ArgumentError, 'wrong number of arguments (0 for 1..n)' if keys.size == 0
37
+
38
+ Settings.new(keys, @target_object, @column_name, @method_name, self)
39
+ end
40
+
41
+ def _get_value(name)
42
+ node = current_node
43
+
44
+ node[name] if node
45
+ end
46
+
47
+ def _set_value(name, v)
48
+ return if _get_value(name) == v
49
+
50
+ @target_object.send("#{@column_name}_will_change!")
51
+ build_nodes
52
+
53
+ if v.nil?
54
+ current_node.delete(name)
55
+ else
56
+ current_node[name] = v
57
+ end
58
+ end
59
+
60
+ def _default_settings(name)
61
+ default_node = KeyTreeBuilder.new(keys, @target_object.class, Configuration::DEFAULTS_COLUMN, parent).current_node
62
+ default_node[name] if default_node
63
+ end
64
+ end
65
+ end
@@ -1,3 +1,3 @@
1
1
  module SettingsOnRails
2
- VERSION = '0.1.30'
2
+ VERSION = '0.2.0'
3
3
  end
@@ -1,6 +1,7 @@
1
1
  require 'settings_on_rails/base'
2
- require 'settings_on_rails/settings_column'
3
- require 'settings_on_rails/settings_handler'
2
+ require 'settings_on_rails/configuration'
3
+ require 'settings_on_rails/settings'
4
+ require 'settings_on_rails/has_settings'
4
5
  require 'settings_on_rails/exceptions'
5
6
  require 'settings_on_rails/version'
6
7
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: settings_on_rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.30
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ALLEN WANG QIANG
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2015-04-09 00:00:00.000000000 Z
11
+ date: 2015-04-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -98,9 +98,11 @@ files:
98
98
  - Rakefile
99
99
  - lib/settings_on_rails.rb
100
100
  - lib/settings_on_rails/base.rb
101
+ - lib/settings_on_rails/configuration.rb
101
102
  - lib/settings_on_rails/exceptions.rb
102
- - lib/settings_on_rails/settings_column.rb
103
- - lib/settings_on_rails/settings_handler.rb
103
+ - lib/settings_on_rails/has_settings.rb
104
+ - lib/settings_on_rails/key_tree_builder.rb
105
+ - lib/settings_on_rails/settings.rb
104
106
  - lib/settings_on_rails/version.rb
105
107
  - settings_on_rails.gemspec
106
108
  homepage: https://github.com/allenwq/settings_on_rails
@@ -1,32 +0,0 @@
1
- module SettingsOnRails
2
- module SettingsColumn
3
- NAME = :__settings_column
4
-
5
- def self.setup(klass, column)
6
- klass.class_eval do
7
- class << self; attr_accessor SettingsColumn::NAME; end
8
-
9
- serialize column, Hash
10
- self.send(SettingsColumn::NAME.to_s + '=', column.to_sym)
11
- end
12
- end
13
-
14
- def self.column(instance)
15
- instance.class.send(SettingsColumn::NAME)
16
- end
17
-
18
- def self.check!(instance)
19
- settings_column = column(instance)
20
- raise NoSettingsColumnError unless settings_column
21
- raise ColumnNotExistError unless instance.has_attribute?(settings_column)
22
- raise InvalidColumnTypeError if column_type_not_text?(instance, settings_column)
23
-
24
- settings_column
25
- end
26
-
27
- private
28
- def self.column_type_not_text?(instance, settings_column)
29
- return true if instance.column_for_attribute(settings_column).try(:sql_type) != 'text'
30
- end
31
- end
32
- end
@@ -1,118 +0,0 @@
1
- module SettingsOnRails
2
- class SettingsHandler
3
- PREFIX = '_'
4
-
5
- attr_accessor :keys, :parent
6
- def initialize(keys, target_object, column, method_name, parent = nil)
7
- @keys = _prefix(keys.dup)
8
- @target_object = target_object
9
- @column = column
10
- @method_name = method_name
11
- @parent = parent
12
-
13
- self.class_eval do
14
- define_method(method_name, instance_method(:_settings))
15
- end
16
- end
17
-
18
- REGEX_SETTER = /\A([a-z]\w*)=\Z/i
19
- REGEX_GETTER = /\A([a-z]\w*)\Z/i
20
-
21
- def respond_to?(method_name, include_priv=false)
22
- super || method_name.to_s =~ REGEX_SETTER
23
- end
24
-
25
- def method_missing(method_name, *args, &block)
26
- if method_name.to_s =~ REGEX_SETTER && args.size == 1
27
- _set_value($1, args.first)
28
- elsif method_name.to_s =~ REGEX_GETTER && args.size == 0
29
- _get_value($1)
30
- else
31
- super
32
- end
33
- end
34
-
35
- private
36
- def _settings(*keys)
37
- raise ArgumentError, 'wrong number of arguments (0 for 1..n)' if keys.size == 0
38
-
39
- SettingsHandler.new(keys, @target_object, @column, @method_name, self)
40
- end
41
-
42
- def _get_value(name)
43
- node = _get_key_node
44
-
45
- if node
46
- node[name]
47
- else
48
- nil
49
- end
50
- end
51
-
52
- def _set_value(name, v)
53
- return if _get_value(name) == v
54
-
55
- @target_object.send("#{@column}_will_change!")
56
- _build_key_tree
57
- node = _get_key_node
58
- if v.nil?
59
- node.delete(name)
60
- else
61
- node[name] = v
62
- end
63
- end
64
-
65
- def _key_node_exist?
66
- value = _target_column
67
-
68
- for key in _key_chain
69
- value = value[key]
70
- return false unless value
71
- end
72
-
73
- true
74
- end
75
-
76
- def _get_key_node
77
- ret = _key_node_exist?
78
- return nil unless ret
79
-
80
- _key_chain.inject(_target_column) { |h, key| h[key] }
81
- end
82
-
83
- def _build_key_tree
84
- value = _target_column
85
-
86
- for key in _key_chain
87
- value[key] = {} unless value[key]
88
- value = value[key]
89
- end
90
- end
91
-
92
- def _target_column
93
- @target_object.read_attribute(@column.to_sym)
94
- end
95
-
96
- # prefix keys with _, to differentiate `settings(:key_a, :key_b)` and settings(:key_a).key_b
97
- # thus _ becomes an reserved field
98
- def _prefix(keys)
99
- for i in 0..(keys.length - 1)
100
- keys[i] = (PREFIX + keys[i].to_s).to_sym
101
- end
102
- keys
103
- end
104
-
105
- # Returns a key chain which includes all parent's keys and self keys
106
- def _key_chain
107
- handler = self
108
- key_chain = []
109
-
110
- begin
111
- key_chain = handler.keys + key_chain
112
- handler = handler.parent
113
- end while handler
114
-
115
- key_chain
116
- end
117
- end
118
- end