stateful_field_for 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +50 -0
- data/Rakefile +6 -0
- data/lib/stateful_field_for.rb +13 -0
- data/lib/stateful_field_for/definition.rb +97 -0
- data/lib/stateful_field_for/railtie.rb +9 -0
- data/lib/stateful_field_for/version.rb +3 -0
- data/spec/fake_app/application.rb +37 -0
- data/spec/fake_app/config.ru +1 -0
- data/spec/fake_app/log/development.log +0 -0
- data/spec/model_spec.rb +85 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/stateful_field_for_spec.rb +7 -0
- metadata +90 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 00e7a7cbc4bee02957da2fa1a4f4d898747a6aad
|
4
|
+
data.tar.gz: f760d30c688490fbd49db1be2d16f792cb084ccc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e33b5b26affe43e1b4cac65ff6ef010f7fa6459c46ed8cbbf88e37b8ade7809063293245e49d1627da80cbf2d327164768a22435cb26a3ed80af490b9e2f1a92
|
7
|
+
data.tar.gz: 5fbd107d095b9a6122d8e3a38d2ea37a6c5ff0dcfed1cf4e5542cb49dc7851caedd8ea9cfc2b29e2ceaa2980c07cb113aa297c2fd1793548361d3f5c610c01b3
|
data/README.md
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# StatefulFieldFor
|
2
|
+
|
3
|
+
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
|
4
|
+
|
5
|
+
Provide the bang and predicate methods for Active Record.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem "stateful_field_for"
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install stateful_field_for
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
```diff
|
26
|
+
class Picture < ApplicationRecord
|
27
|
+
+ stateful_field_for :hidden_at, scope: true do
|
28
|
+
+ enable :hide, to: :hidden
|
29
|
+
+ disable :show, to: :visible, initial: true
|
30
|
+
+ end
|
31
|
+
end
|
32
|
+
```
|
33
|
+
|
34
|
+
Then you can use the bang and predicate methods:
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
picture = Picture.create
|
38
|
+
|
39
|
+
picture.show!
|
40
|
+
picture.hidden? # false
|
41
|
+
picture.visible? # true
|
42
|
+
|
43
|
+
picture.hide!
|
44
|
+
picture.hidden? # true
|
45
|
+
picture.visible? # false
|
46
|
+
```
|
47
|
+
|
48
|
+
## License
|
49
|
+
|
50
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require "stateful_field_for/version"
|
2
|
+
|
3
|
+
module StatefulFieldFor
|
4
|
+
def stateful_field_for(attr_name, options = {}, &block)
|
5
|
+
definition = Definition.new(attr_name, options)
|
6
|
+
definition.instance_eval(&block)
|
7
|
+
const_set definition.module_name, definition._module
|
8
|
+
include definition._module
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
require "stateful_field_for/definition"
|
13
|
+
require "stateful_field_for/railtie" if defined?(Rails)
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module StatefulFieldFor
|
2
|
+
class Definition
|
3
|
+
attr_reader :attr_name, :prefix, :suffix, :scope, :logic
|
4
|
+
|
5
|
+
def initialize(attr_name, options = {})
|
6
|
+
@attr_name = attr_name
|
7
|
+
@prefix = options[:prefix]
|
8
|
+
@suffix = options[:suffix]
|
9
|
+
@scope = options[:scope] || false
|
10
|
+
@logic = (options[:type] == :date) ? "Date.today" : "Time.current"
|
11
|
+
end
|
12
|
+
|
13
|
+
def _module
|
14
|
+
@_module ||= Module.new do
|
15
|
+
def self.included(klass)
|
16
|
+
__define_scope_for_activate(klass) if respond_to?(:__define_scope_for_activate)
|
17
|
+
__define_scope_for_deactivate(klass) if respond_to?(:__define_scope_for_deactivate)
|
18
|
+
__define_after_initialize(klass) if respond_to?(:__define_after_initialize)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def module_name
|
24
|
+
"generated_#{attr_name}_stateful_methods".split("_").map(&:camelize).join
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def enable(event_basename, to: nil, initial: false)
|
30
|
+
event = format_name(event_basename)
|
31
|
+
state = to.nil? ? event : format_name(to)
|
32
|
+
|
33
|
+
_module.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
34
|
+
def #{state}?
|
35
|
+
!#{attr_name}.nil?
|
36
|
+
end
|
37
|
+
|
38
|
+
def #{event}
|
39
|
+
self.#{attr_name} = #{logic}
|
40
|
+
end
|
41
|
+
|
42
|
+
def #{event}!(options = {})
|
43
|
+
#{event}
|
44
|
+
save!(options)
|
45
|
+
end
|
46
|
+
RUBY
|
47
|
+
|
48
|
+
_module.module_eval <<-RUBY, __FILE__, __LINE__ + 1 if scope
|
49
|
+
def self.__define_scope_for_activate(klass)
|
50
|
+
klass.scope :#{state}, -> { where.not(#{attr_name}: nil) }
|
51
|
+
end
|
52
|
+
RUBY
|
53
|
+
|
54
|
+
_module.module_eval <<-RUBY, __FILE__, __LINE__ + 1 if initial
|
55
|
+
def self.__define_after_initialize(klass)
|
56
|
+
klass.after_initialize :#{event}
|
57
|
+
end
|
58
|
+
RUBY
|
59
|
+
end
|
60
|
+
|
61
|
+
def disable(event_basename, to: nil, initial: false)
|
62
|
+
event = format_name(event_basename)
|
63
|
+
state = to.nil? ? event : format_name(to)
|
64
|
+
|
65
|
+
_module.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
66
|
+
def #{state}?
|
67
|
+
#{attr_name}.nil?
|
68
|
+
end
|
69
|
+
|
70
|
+
def #{event}
|
71
|
+
self.#{attr_name} = nil
|
72
|
+
end
|
73
|
+
|
74
|
+
def #{event}!(options = {})
|
75
|
+
#{event}
|
76
|
+
save!(options)
|
77
|
+
end
|
78
|
+
RUBY
|
79
|
+
|
80
|
+
_module.module_eval <<-RUBY, __FILE__, __LINE__ + 1 if scope
|
81
|
+
def self.__define_scope_for_deactivate(klass)
|
82
|
+
klass.scope :#{state}, -> { where(#{attr_name}: nil) }
|
83
|
+
end
|
84
|
+
RUBY
|
85
|
+
|
86
|
+
_module.module_eval <<-RUBY, __FILE__, __LINE__ + 1 if initial
|
87
|
+
def self.__define_after_initialize(klass)
|
88
|
+
klass.after_initialize :#{event}
|
89
|
+
end
|
90
|
+
RUBY
|
91
|
+
end
|
92
|
+
|
93
|
+
def format_name(basename)
|
94
|
+
[prefix, basename, suffix].compact.join("_")
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require "active_record"
|
2
|
+
require "action_controller"
|
3
|
+
require "stateful_field_for"
|
4
|
+
|
5
|
+
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
|
6
|
+
|
7
|
+
module FakeApp
|
8
|
+
class Application < Rails::Application
|
9
|
+
config.eager_load = false
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
FakeApp::Application.initialize!
|
14
|
+
|
15
|
+
class CreateTables < ActiveRecord::Migration[5.1]
|
16
|
+
def change
|
17
|
+
create_table :pictures do |t|
|
18
|
+
t.string :title
|
19
|
+
t.datetime :published_at
|
20
|
+
t.datetime :hidden_at
|
21
|
+
|
22
|
+
t.timestamps
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class Picture < ActiveRecord::Base
|
28
|
+
stateful_field_for :published_at, scope: true do
|
29
|
+
enable :publish, to: :published, initial: true
|
30
|
+
disable :unpublish, to: :unpublished
|
31
|
+
end
|
32
|
+
|
33
|
+
stateful_field_for :hidden_at do
|
34
|
+
enable :hide, to: :hidden
|
35
|
+
disable :show, to: :visible
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
run Rails.application
|
File without changes
|
data/spec/model_spec.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe Picture do
|
4
|
+
context "scope" do
|
5
|
+
let!(:published_records) { Array.new(3) { Picture.create(published_at: Time.current) } }
|
6
|
+
let!(:unpublished_records) { Array.new(3) { Picture.create.tap(&:unpublish!) } }
|
7
|
+
|
8
|
+
describe ".published" do
|
9
|
+
subject { Picture.published.pluck(:id) }
|
10
|
+
it { is_expected.to match_array published_records.map(&:id) }
|
11
|
+
end
|
12
|
+
|
13
|
+
describe ".unpublished" do
|
14
|
+
subject { Picture.unpublished.pluck(:id) }
|
15
|
+
it { is_expected.to match_array unpublished_records.map(&:id) }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context "callback" do
|
20
|
+
let(:instance) { Picture.new }
|
21
|
+
subject { instance.published? }
|
22
|
+
it { is_expected.to be true }
|
23
|
+
end
|
24
|
+
|
25
|
+
context "predicate" do
|
26
|
+
let(:published) { Picture.new(published_at: Time.current) }
|
27
|
+
let(:unpublished) { Picture.new.tap(&:unpublish) }
|
28
|
+
let(:hidden) { Picture.new(hidden_at: Time.current) }
|
29
|
+
let(:visible) { Picture.new(hidden_at: nil) }
|
30
|
+
|
31
|
+
describe "#published?" do
|
32
|
+
it { expect(published.published?).to be true }
|
33
|
+
it { expect(unpublished.published?).to be false }
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "#unpublished?" do
|
37
|
+
it { expect(unpublished.unpublished?).to be true }
|
38
|
+
it { expect(published.unpublished?).to be false }
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "#hidden?" do
|
42
|
+
it { expect(hidden.hidden?).to be true }
|
43
|
+
it { expect(visible.hidden?).to be false }
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "#visible?" do
|
47
|
+
it { expect(visible.visible?).to be true }
|
48
|
+
it { expect(hidden.visible?).to be false }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context "bang" do
|
53
|
+
let(:published) { Picture.new(published_at: Time.current) }
|
54
|
+
let(:hidden) { Picture.new(hidden_at: Time.current) }
|
55
|
+
let(:visible) { Picture.new(hidden_at: nil) }
|
56
|
+
|
57
|
+
describe "#publish!" do
|
58
|
+
let(:instance) { Picture.new(published_at: nil) }
|
59
|
+
before { instance.publish! }
|
60
|
+
it { expect(instance.published?).to be true }
|
61
|
+
it { expect(instance.persisted?).to be true }
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "#unpublish!" do
|
65
|
+
let(:instance) { Picture.new(published_at: Time.current) }
|
66
|
+
before { instance.unpublish! }
|
67
|
+
it { expect(instance.unpublished?).to be true }
|
68
|
+
it { expect(instance.persisted?).to be true }
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "#hide!" do
|
72
|
+
let(:instance) { Picture.new(hidden_at: nil) }
|
73
|
+
before { instance.hide! }
|
74
|
+
it { expect(instance.hidden?).to be true }
|
75
|
+
it { expect(instance.persisted?).to be true }
|
76
|
+
end
|
77
|
+
|
78
|
+
describe "#show!" do
|
79
|
+
let(:instance) { Picture.new(hidden_at: Time.current) }
|
80
|
+
before { instance.show! }
|
81
|
+
it { expect(instance.visible?).to be true }
|
82
|
+
it { expect(instance.persisted?).to be true }
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require "bundler/setup"
|
2
|
+
# load Rails first
|
3
|
+
require "rails"
|
4
|
+
|
5
|
+
# needs to load the app next
|
6
|
+
require_relative "fake_app/application"
|
7
|
+
|
8
|
+
require "stateful_field_for"
|
9
|
+
require "rspec/rails"
|
10
|
+
require "pry"
|
11
|
+
|
12
|
+
RSpec.configure do |config|
|
13
|
+
config.example_status_persistence_file_path = ".rspec_status"
|
14
|
+
config.disable_monkey_patching!
|
15
|
+
config.use_transactional_fixtures = true
|
16
|
+
|
17
|
+
config.expect_with :rspec do |c|
|
18
|
+
c.syntax = :expect
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
CreateTables.migrate(:up) unless ActiveRecord::Base.connection.table_exists?("pictures")
|
metadata
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: stateful_field_for
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Yoshiyuki Hirano
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-10-22 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: railties
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '5.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.15'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.15'
|
41
|
+
description: Provide the bang and predicate methods for Active Record
|
42
|
+
email:
|
43
|
+
- yhirano@me.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- README.md
|
49
|
+
- Rakefile
|
50
|
+
- lib/stateful_field_for.rb
|
51
|
+
- lib/stateful_field_for/definition.rb
|
52
|
+
- lib/stateful_field_for/railtie.rb
|
53
|
+
- lib/stateful_field_for/version.rb
|
54
|
+
- spec/fake_app/application.rb
|
55
|
+
- spec/fake_app/config.ru
|
56
|
+
- spec/fake_app/log/development.log
|
57
|
+
- spec/model_spec.rb
|
58
|
+
- spec/spec_helper.rb
|
59
|
+
- spec/stateful_field_for_spec.rb
|
60
|
+
homepage: https://github.com/yhirano55/stateful_field_for
|
61
|
+
licenses:
|
62
|
+
- MIT
|
63
|
+
metadata: {}
|
64
|
+
post_install_message:
|
65
|
+
rdoc_options: []
|
66
|
+
require_paths:
|
67
|
+
- lib
|
68
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: 2.2.0
|
73
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
requirements: []
|
79
|
+
rubyforge_project:
|
80
|
+
rubygems_version: 2.6.13
|
81
|
+
signing_key:
|
82
|
+
specification_version: 4
|
83
|
+
summary: Provide the bang and predicate methods for Active Record
|
84
|
+
test_files:
|
85
|
+
- spec/fake_app/application.rb
|
86
|
+
- spec/fake_app/config.ru
|
87
|
+
- spec/fake_app/log/development.log
|
88
|
+
- spec/model_spec.rb
|
89
|
+
- spec/spec_helper.rb
|
90
|
+
- spec/stateful_field_for_spec.rb
|