has_tokenable 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 9031526dff08471220b84d58ed37738fa3f70760c9ab1769ef450449e395e7bf
4
+ data.tar.gz: d00ec6c32f4aec5e802801340d51e193600d4b29a806242bef23761a553b74ab
5
+ SHA512:
6
+ metadata.gz: e2c151f14038955e82d34faed45541ab7496c7a58203b3f72ffdb9af2626c48645811ce231da110363e10e65a1cbaabae5c10c792792f52cf5a3d798e34bbc7e
7
+ data.tar.gz: cf06d4088ba210c159e58cf5bf05be2f8527cc73b31f183e9b40a1d8cb39f83521d45d706a510c8d50c6947f8cb8b39ff30e51f2a0241d2d95c05c9538a3013f
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source "http://rubygems.org"
2
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,9 @@
1
+ Copyright (c) 2019 Carlos Montalvo.
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5
+
6
+ Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7
+ Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8
+
9
+ THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,129 @@
1
+ Identify your active records with random tokens when you don't want your users to see a sequential ID.
2
+
3
+
4
+ ------------------------------------------------------------------------------
5
+ Installation
6
+ ------------------------------------------------------------------------------
7
+
8
+ Add has_tokenable to your Gemfile like so:
9
+
10
+ ```ruby
11
+ gem 'has_tokenable', '~> 0.1.0'
12
+ ```
13
+
14
+ Now run `bundle install` and you're good to go!
15
+
16
+
17
+ ------------------------------------------------------------------------------
18
+ Usage
19
+ ------------------------------------------------------------------------------
20
+
21
+ First, add a token to your model's table with a migration:
22
+
23
+ ```ruby
24
+ # Upgrade and existing table
25
+ class AddTokenToItems < ActiveRecord::Migration
26
+ add_column :items, :token, :string
27
+ end
28
+
29
+ # Add to a new table
30
+ class CreateItems < ActiveRecord::Migration
31
+ def change
32
+ create_table :items do |t|
33
+ t.token
34
+ t.string :name
35
+
36
+ t.timestamps
37
+ end
38
+ end
39
+ end
40
+ ```
41
+
42
+
43
+ Now make sure your model knows to use it's token by calling `has_tokenable`
44
+
45
+ ```ruby
46
+ class Item < ActiveRecord::Base
47
+ has_tokenable
48
+ end
49
+ ```
50
+
51
+ That's basically it! Your Items will now know to use their token as their identifier.
52
+
53
+ Try it out in your `rails console`
54
+
55
+ ```ruby
56
+ @item = Item.create(name: "Tokenz!")
57
+ #<Item id: 1, token: "B5OvJvy6B2_DZg", name: "Tokenz!", created_at: "2020-01-26 20:17:13", updated_at: "2020-01-26 20:17:13">
58
+ @item.to_param
59
+ # B5OvJvy6B2_DZg
60
+ @item == Item.find("B5OvJvy6B2_DZg")
61
+ # true
62
+ ```
63
+
64
+
65
+ ------------------------------------------------------------------------------
66
+ Options
67
+ ------------------------------------------------------------------------------
68
+
69
+ You can customize has_tokenable by setting a few options. Here's the defaults:
70
+
71
+ ```ruby
72
+ {
73
+ prefix: nil, # if nil use first letter of class name
74
+ length: 10,
75
+ param_name: 'token',
76
+ method_random: 'urlsafe_base64' #method random urlsafe_base64 hex alphanumeric random_number uuid
77
+ }
78
+ ```
79
+
80
+
81
+ Options can be set globally by overwriting the `HasTokenable.default_token_options`
82
+
83
+ ```ruby
84
+ # config/initializers/has_tokenable.rb
85
+
86
+ # for one option
87
+ HasTokenable.default_token_options[:prefix] = "OMG"
88
+
89
+ # for multiple options
90
+ HasTokenable.default_token_options.merge!(
91
+ method_random: 'alphanumeric',
92
+ length: 8
93
+ )
94
+ ```
95
+
96
+ Options can also be set on a per-class level:
97
+
98
+ ```ruby
99
+ class List < ActiveRecord::Base
100
+ has_tokenable prefix: "LI", length: 10
101
+ end
102
+
103
+ class Item < ActiveRecord::Base
104
+ has_tokenable prefix: "ITM"
105
+ end
106
+ ```
107
+
108
+
109
+ ------------------------------------------------------------------------------
110
+ Demo
111
+ ------------------------------------------------------------------------------
112
+
113
+ Try out the demo to get a real clear idea of what has_tokenable does.
114
+
115
+ ```bash
116
+ git clone git://github.com/CarlangasGO/has_tokenable.git
117
+ cd has_tokenable
118
+ bundle install
119
+ rails s
120
+ ```
121
+
122
+ Now open your browser to [http://localhost:3000](http://localhost:3000)
123
+
124
+
125
+ ------------------------------------------------------------------------------
126
+ License
127
+ ------------------------------------------------------------------------------
128
+
129
+ Copyright (c) 2019 - 2020, released under the New BSD License All rights reserved.
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ require 'bundler'
2
+ require 'rake/testtask'
3
+
4
+ Bundler::GemHelper.install_tasks
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << 'test' << 'lib'
8
+ t.pattern = 'test/**/*_test.rb'
9
+ t.verbose = true
10
+ end
11
+
12
+ task :default => :test
@@ -0,0 +1,33 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "has_tokenable/version"
4
+
5
+ Gem::Specification.new do |s|
6
+
7
+ s.name = %q{has_tokenable}
8
+ s.version = HasTokenable::VERSION
9
+ s.platform = Gem::Platform::RUBY
10
+ s.authors = ["Carlos Montalvo"]
11
+ s.email = ["carlosmontalvo@zetanova.com"]
12
+ s.homepage = "https://github.com/CarlangasGO/has_tokenable"
13
+ s.summary = %q{Identifies your active records with a random token.}
14
+ s.description = %q{Identifies your active records with a random token. For more information, please see the documentation.}
15
+ s.license = "BSD-2-Clause"
16
+ s.metadata = { "source_code_uri" => "https://github.com/CarlangasGO/has_tokenable" }
17
+
18
+
19
+ s.files = `git ls-files`.split("\n")
20
+
21
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
22
+ s.require_paths = ["lib"]
23
+
24
+
25
+ s.require_paths = ["lib"]
26
+
27
+ s.add_dependency('activerecord', '~> 4')
28
+ s.add_dependency('activesupport', '~> 4')
29
+
30
+ s.add_development_dependency('rails', '~> 4')
31
+ s.add_development_dependency('sqlite3', '~> 1.3')
32
+
33
+ end
@@ -0,0 +1,77 @@
1
+ module HasTokenable
2
+ module Concern
3
+
4
+ METHODS = %w(urlsafe_base64 hex alphanumeric random_number uuid).freeze
5
+
6
+ def self.included(base)
7
+ base.send(:extend, ClassMethods)
8
+ base.send(:include, InstanceMethods)
9
+ base.class_eval do
10
+ validates :token, presence: true, uniqueness: true
11
+ before_validation :generate_token, on: :create, if: proc{|record| record.token.nil? }
12
+ end
13
+ end
14
+
15
+ module ClassMethods
16
+
17
+ def method_random=(value)
18
+ values = value.is_a?(Array) ? value : [value]
19
+ values.map! { |v| v.to_s }
20
+
21
+ raise ArgumentError.new('Method is not supported') if (values - METHODS).size > 0
22
+ values
23
+ end
24
+
25
+ # Default options as well as an overwrite point so you can assign different defaults to different models
26
+ def default_token_options
27
+ @default_token_options ||= begin
28
+ options = HasTokenable.default_token_options
29
+ options[:prefix] ||= self.name[0, 1]
30
+ options
31
+ end
32
+ end
33
+
34
+ def generate_method(options)
35
+ args = [ options[:method_random], options[:length].to_i ]
36
+
37
+ args.slice!(-1) if options[:method_random] == 'uuid'
38
+
39
+ SecureRandom.send(*args)
40
+ end
41
+
42
+ # Generates a unique token based on the options
43
+ def generate_unique_token
44
+ record, options = true, @has_tokenable_options
45
+ conditions = {}
46
+
47
+ token = loop do
48
+ random_token = self.generate_method(options)
49
+
50
+ conditions[options[:param_name].to_sym] = random_token
51
+
52
+ break random_token unless self.exists?(conditions)
53
+ end
54
+
55
+ token
56
+ end
57
+
58
+ end # ClassMethods
59
+
60
+ module InstanceMethods
61
+
62
+ # Returns the resource's token
63
+ def to_param
64
+ self.send(self.class.has_tokenable_options[:param_name])
65
+ end
66
+
67
+ private
68
+
69
+ def generate_token
70
+ self.token = self.class.generate_unique_token
71
+ end
72
+
73
+ end # InstanceMethods
74
+
75
+ end # Concern
76
+
77
+ end # HasTokenable
@@ -0,0 +1,44 @@
1
+ module HasTokenable
2
+ module FinderMethods
3
+
4
+ # Find by token ensuring case sensitivity
5
+ def find_by_case_sensitive_token(token)
6
+ return if token.nil?
7
+ where("#{token_with_table_name} = ?", token).first
8
+ end
9
+
10
+ # Find by token
11
+ def find_by_token(token)
12
+ return if token.nil?
13
+ send(:find_by_case_sensitive_token , token)
14
+ end
15
+
16
+ # Find by token and raise error if no record is found
17
+ def find_by_token!(token)
18
+ record = find_by_token(token)
19
+ raise ActiveRecord::RecordNotFound, "Could not find #{self.name} with token #{token.inspect}" if record.nil?
20
+ record
21
+ end
22
+
23
+ # Find by token if the first param looks like a token, otherwise use super
24
+ def find(*args)
25
+ if args[0].is_a?(String) && args[0].length == has_tokenable_options[:length]
26
+ record = find_by_token(args[0])
27
+ end
28
+ record || super(*args)
29
+ end
30
+
31
+ def find!(*args)
32
+ record = find(*args)
33
+ raise ActiveRecord::RecordNotFound if record.nil?
34
+ record
35
+ end
36
+
37
+ private
38
+
39
+ def token_with_table_name
40
+ "#{table_name}.#{has_tokenable_options[:param_name]}"
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,15 @@
1
+ require 'active_record/connection_adapters/abstract/schema_definitions'
2
+
3
+ module HasTokenable
4
+ module TableDefinition
5
+
6
+ def token(*args)
7
+ options = { length: HasTokenable.default_token_options[:length] }.merge(args.extract_options!)
8
+ puts "opeiont: #{options}"
9
+ column(:token, :string, options.merge(nil: false))
10
+ end
11
+
12
+ end
13
+ end
14
+
15
+ ActiveRecord::ConnectionAdapters::TableDefinition.send(:include, HasTokenable::TableDefinition)
@@ -0,0 +1,3 @@
1
+ module HasTokenable
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,31 @@
1
+ require 'has_tokenable/concern'
2
+ require 'has_tokenable/finder_methods'
3
+ require 'has_tokenable/table_definition'
4
+
5
+ module HasTokenable
6
+
7
+ module ActiveRecordTie
8
+
9
+ def has_tokenable(options={})
10
+ self.send(:include, HasTokenable::Concern)
11
+ @has_tokenable_options ||= HasTokenable.default_token_options.merge(options)
12
+ end
13
+
14
+ def has_tokenable_options
15
+ @has_tokenable_options ||= HasTokenable.default_token_options
16
+ end
17
+ end
18
+
19
+ def self.default_token_options
20
+ @default_token_options ||= {
21
+ :prefix => nil, # if nil use first letter of class name
22
+ :length => 10,
23
+ :param_name => 'token',
24
+ :method_random => 'urlsafe_base64'
25
+ }
26
+ end
27
+
28
+ end
29
+
30
+ ActiveRecord::Base.send(:extend, HasTokenable::ActiveRecordTie)
31
+ ActiveRecord::Base.send(:extend, HasTokenable::FinderMethods)
metadata ADDED
@@ -0,0 +1,113 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: has_tokenable
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Carlos Montalvo
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-04-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '4'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '4'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rails
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '4'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '4'
55
+ - !ruby/object:Gem::Dependency
56
+ name: sqlite3
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.3'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.3'
69
+ description: Identifies your active records with a random token. For more information,
70
+ please see the documentation.
71
+ email:
72
+ - carlosmontalvo@zetanova.com
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".gitignore"
78
+ - Gemfile
79
+ - LICENSE
80
+ - README.md
81
+ - Rakefile
82
+ - has_tokenable.gemspec
83
+ - lib/has_tokenable.rb
84
+ - lib/has_tokenable/concern.rb
85
+ - lib/has_tokenable/finder_methods.rb
86
+ - lib/has_tokenable/table_definition.rb
87
+ - lib/has_tokenable/version.rb
88
+ homepage: https://github.com/CarlangasGO/has_tokenable
89
+ licenses:
90
+ - BSD-2-Clause
91
+ metadata:
92
+ source_code_uri: https://github.com/CarlangasGO/has_tokenable
93
+ post_install_message:
94
+ rdoc_options: []
95
+ require_paths:
96
+ - lib
97
+ required_ruby_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ requirements: []
108
+ rubyforge_project:
109
+ rubygems_version: 2.7.6.2
110
+ signing_key:
111
+ specification_version: 4
112
+ summary: Identifies your active records with a random token.
113
+ test_files: []