findable 0.0.3 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ed1492954c92884d4fcf68884ef79e9137607919
4
- data.tar.gz: f7118ffa85352d988fc77af88128246edaf8ebd8
3
+ metadata.gz: 3d9b4cc9d5f9ac6949eaa3db65f5dc97359a8450
4
+ data.tar.gz: 3ca9a61d80ea9b8d74f5a4e2df78082f07083012
5
5
  SHA512:
6
- metadata.gz: c85b0d1bcf815bfa2922e3f4504e8deae8b9b335e5fa0fd889883bf004d263d8a5836df74be55c81517b5d68be82cf55e8144b6535e71fd9e609febdbb460a40
7
- data.tar.gz: 90b596946d2344d52c0ae952bdbc62ed252e1222888d359377c3fd9deb06becf16d31b5e605b560ef889ec29d404be415c6cb371478f93b23026ac043a979729
6
+ metadata.gz: 68ab5f2cb1c8b3fc443ff1b4d1c8199a07634d3fb7ad08a7587247c0ee9836d711f596e2286566f82e59c9dccb2d815098e84e95adc665b0c878d3a721461a1c
7
+ data.tar.gz: fdd8b84c3f07ded341d312cc6bd1117f2f64b61f5dc40865fa36877852c27b17562b6b2ff3bcb086344ec7e61317e01f058300c53d745b635e17658363793060
data/.travis.yml CHANGED
@@ -1,14 +1,10 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.1.0
4
- - 2.2.0
5
- branches:
6
- only:
7
- - master
3
+ - 2.1.5
4
+ - 2.2.1
8
5
  gemfile:
9
6
  - Gemfile
10
7
  script: bundle exec rake spec
11
- notifications:
12
- mails:
13
- - i2bskn@gmail.com
8
+ services:
9
+ - redis-server
14
10
 
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- # Specify your gem's dependencies in findable.gemspec
4
3
  gemspec
4
+
data/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  [![Coverage Status](https://img.shields.io/coveralls/i2bskn/findable.svg)](https://coveralls.io/r/i2bskn/findable)
6
6
  [![Code Climate](https://codeclimate.com/github/i2bskn/findable/badges/gpa.svg)](https://codeclimate.com/github/i2bskn/findable)
7
7
 
8
- Redis wrapper with API like ActiveRecord. (While creating...)
8
+ Redis wrapper with API like ActiveRecord.
9
9
 
10
10
  ## Installation
11
11
 
@@ -17,27 +17,79 @@ gem "findable"
17
17
 
18
18
  And then execute:
19
19
 
20
- $ bundle
20
+ ```
21
+ $ bundle
22
+ ```
23
+
24
+ Setup config file and seed script:
25
+
26
+ ```
27
+ $ rails generate findable:install
28
+ ```
29
+
30
+ Added following files:
31
+
32
+ - `config/initializers/findable.rb`
33
+ - Config file for Findable.
34
+ - `db/findable_seeds.rb`
35
+ - Seed script for Findable.
36
+ - `db/findable_seeds/.keep`
37
+ - Directory for storing seed files of Findable.
21
38
 
22
39
  ## Usage
23
40
 
41
+ Create seed file if static data.
42
+
43
+ Example `db/findable_seeds/tags.yml`:
44
+
45
+ ```yaml
46
+ data1:
47
+ id: 1
48
+ name: Ruby
49
+ ```
50
+
51
+ Create model.
52
+
53
+ Example `app/models/tag.rb`:
54
+
24
55
  ```ruby
25
- class Company < ActiveRecord::Base
26
- has_many :person
56
+ class Tag < Findable::Base
27
57
  end
58
+ ```
28
59
 
29
- class Person < Findable::Base
30
- fields :name, :email, :gender, :company_id
31
- belongs_to :company
32
- end
60
+ Execute seed script if you create seed files.
33
61
 
34
- person = Person.new(name: "Ken Iiboshi", gender: "male")
35
- person.email = "i2bskn@example.com"
36
- person.save
62
+ ```
63
+ $ rake findable:seeds
64
+ ```
65
+
66
+ Manipulate data with API like ActiveRecord.
67
+
68
+ ```
69
+ $ rails console
70
+ pry(main)> Tag.find(1)
71
+ => #<Tag:0x00000108068430 @_attributes={:id=>1, :name=>"Ruby"}>
72
+ pry(main)> golang = Tag.create(name: "Go")
73
+ => #<Tag:0x00000107ff6420 @_attributes={:name=>"Go", :id=>2}>
74
+ pry(main)> Tag.all.each {|tag| p tag.name }
75
+ "Ruby"
76
+ "Go"
77
+ => [#<Tag:0x00000107f49568 @_attributes={:id=>1, :name=>"Ruby"}>, #<Tag:0x00000107f492e8 @_attributes={:name=>"Go", :id=>2}>]
78
+ ```
79
+
80
+ ## Associations
81
+
82
+ It is possible to use the `belongs_to` and `has_one` and `has_many`.
83
+ Mutually can refer to objects of ActiveRecord and Findable.
84
+
85
+ ```ruby
86
+ class Article < ActiveRecord::Base
87
+ include Findable::Associations::ActiveRecordExt
88
+ belongs_to :tag
89
+ end
37
90
 
38
- people = Person.where(gender: "male")
39
- people.each do |person|
40
- puts person.name
91
+ class Tag < Findable::Base
92
+ has_many :articles
41
93
  end
42
94
  ```
43
95
 
data/Rakefile CHANGED
@@ -1,10 +1,15 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rspec/core/rake_task"
3
3
 
4
+ task :default => :spec
5
+
4
6
  desc "Run all specs"
5
7
  RSpec::Core::RakeTask.new(:spec) do |spec|
6
8
  spec.pattern = FileList['spec/**/*_spec.rb']
7
9
  end
8
10
 
9
- task :default => :spec
11
+ desc "Console with library"
12
+ task :console do
13
+ sh "pry -I lib -r bundler/setup -r findable"
14
+ end
10
15
 
data/findable.gemspec CHANGED
@@ -1,3 +1,4 @@
1
+ # coding: utf-8
1
2
  lib = File.expand_path('../lib', __FILE__)
2
3
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
4
  require 'findable/version'
@@ -19,13 +20,13 @@ Gem::Specification.new do |spec|
19
20
 
20
21
  spec.add_dependency "activesupport"
21
22
  spec.add_dependency "activemodel"
22
- spec.add_dependency "redis"
23
+ spec.add_dependency "redis-objects"
23
24
  spec.add_dependency "oj"
24
25
 
25
26
  spec.add_development_dependency "bundler"
26
27
  spec.add_development_dependency "rake"
27
28
  spec.add_development_dependency "rspec-rails"
28
- spec.add_development_dependency "simplecov"
29
+ spec.add_development_dependency "pry"
29
30
  spec.add_development_dependency "coveralls"
30
31
  end
31
32
 
data/lib/findable.rb CHANGED
@@ -1,13 +1,12 @@
1
1
  require "active_support"
2
2
  require "active_model"
3
- require "redis"
3
+ require "redis/objects"
4
4
  require "oj"
5
5
 
6
6
  require "findable/version"
7
7
  require "findable/errors"
8
8
  require "findable/configuration"
9
- require "findable/serializer"
10
- require "findable/association"
9
+ require "findable/query"
11
10
  require "findable/base"
12
11
  require "findable/railtie" if defined?(Rails)
13
12
 
@@ -0,0 +1,52 @@
1
+ require "findable/associations/utils"
2
+
3
+ module Findable
4
+ module Associations
5
+ extend ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ def has_many(*args)
9
+ name, options = Utils.parse_args(args)
10
+ model = Utils.model_for(name, collection: true, **options)
11
+ foreign_key = options[:foreign_key].presence || model_name.name.foreign_key
12
+
13
+ define_method(name) do
14
+ model.where(foreign_key => public_send(self.class.primary_key))
15
+ end
16
+ end
17
+
18
+ def has_one(*args)
19
+ name, options = Utils.parse_args(args)
20
+ model = Utils.model_for(name, **options)
21
+ foreign_key = options[:foreign_key].presence || model_name.name.foreign_key
22
+
23
+ define_method(name) do
24
+ model.find_by(foreign_key => public_send(self.class.primary_key))
25
+ end
26
+ end
27
+
28
+ def belongs_to(*args)
29
+ name, options = Utils.parse_args(args)
30
+ model = Utils.model_for(name, safe: true, **options)
31
+ foreign_key = options[:foreign_key].presence || name.to_s.foreign_key
32
+
33
+ if options[:polymorphic]
34
+ define_method(name) do
35
+ public_send("#{name}_type").constantize.find(public_send(foreign_key))
36
+ end
37
+ else
38
+ define_field(foreign_key)
39
+
40
+ define_method(name) do
41
+ model.find_by(model.primary_key => public_send(foreign_key))
42
+ end
43
+
44
+ define_method("#{name}=") do |value|
45
+ attributes[foreign_key.to_sym] = value ? value.public_send(model.primary_key) : nil
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+
@@ -0,0 +1,87 @@
1
+ module Findable
2
+ module Associations
3
+ module ActiveRecordExt
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ def has_many(*args)
8
+ name, options = Utils.parse_args(args)
9
+ model = Utils.model_for(name, collection: true, safe: true, **options)
10
+
11
+ if model && model < Findable::Base
12
+ foreign_key = options[:foreign_key].presence || model_name.name.foreign_key
13
+
14
+ define_method(name) do
15
+ model.where(foreign_key => public_send(self.class.primary_key))
16
+ end
17
+
18
+ reflection = ActiveRecord::Reflection.create(
19
+ :has_many,
20
+ name.to_sym,
21
+ nil,
22
+ options,
23
+ self,
24
+ )
25
+ ActiveRecord::Reflection.add_reflection(self, name.to_sym, reflection)
26
+ else
27
+ super
28
+ end
29
+ end
30
+
31
+ def has_one(*args)
32
+ name, options = Utils.parse_args(args)
33
+ model = Utils.model_for(name, safe: true, **options)
34
+
35
+ if model && model < Findable::Base
36
+ foreign_key = options[:foreign_key].presence || model_name.name.foreign_key
37
+
38
+ define_method(name) do
39
+ model.find_by(foreign_key => public_send(self.class.primary_key))
40
+ end
41
+
42
+ reflection = ActiveRecord::Reflection.create(
43
+ :has_one,
44
+ name.to_sym,
45
+ nil,
46
+ options,
47
+ self,
48
+ )
49
+ ActiveRecord::Reflection.add_reflection(self, name.to_sym, reflection)
50
+ else
51
+ super
52
+ end
53
+ end
54
+
55
+ def belongs_to(*args)
56
+ name, options = Utils.parse_args(args)
57
+ model = Utils.model_for(name, safe: true, **options)
58
+
59
+ if model && model < Findable::Base
60
+ foreign_key = options[:foreign_key].presence || name.to_s.foreign_key
61
+
62
+ define_method(name) do
63
+ model.find_by(model.primary_key => public_send(foreign_key))
64
+ end
65
+
66
+ define_method("#{name}=") do |value|
67
+ value = value ? value.public_send(model.primary_key) : nil
68
+ public_send("#{foreign_key}=", value)
69
+ end
70
+
71
+ reflection = ActiveRecord::Reflection.create(
72
+ :belongs_to,
73
+ name.to_sym,
74
+ nil,
75
+ options,
76
+ self,
77
+ )
78
+ ActiveRecord::Reflection.add_reflection(self, name.to_sym, reflection)
79
+ else
80
+ super
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+
@@ -0,0 +1,25 @@
1
+ module Findable
2
+ module Associations
3
+ class Utils
4
+ def self.model_for(name, options = {})
5
+ unless model_name = options[:class_name].presence
6
+ name = options[:collection] ? name.to_s.singularize : name.to_s
7
+ model_name = name.camelize
8
+ end
9
+
10
+ if options[:safe]
11
+ model_name.try(:safe_constantize)
12
+ else
13
+ model_name.constantize
14
+ end
15
+ end
16
+
17
+ def self.parse_args(args)
18
+ copied = args.dup
19
+ options = copied.extract_options!
20
+ [copied.first, options]
21
+ end
22
+ end
23
+ end
24
+ end
25
+
data/lib/findable/base.rb CHANGED
@@ -1,81 +1,95 @@
1
- require "findable/recordable"
2
- require "findable/connection"
3
- require "findable/namespace"
1
+ require "findable/associations"
4
2
 
5
3
  module Findable
6
4
  class Base
7
5
  include ActiveModel::Model
8
- include Recordable
9
- include Connection
10
- include Namespace
11
- extend Association
6
+ include ActiveModel::AttributeMethods
7
+
8
+ include Associations
9
+
10
+ attribute_method_suffix "="
11
+ attribute_method_suffix "?"
12
12
 
13
13
  class << self
14
+ ## field definitions
15
+
16
+ def define_field(attr, options = {})
17
+ unless public_method_defined?(attr)
18
+ options.symbolize_keys!
19
+ define_attribute_methods attr
20
+ define_method(attr) { attributes[attr.to_sym] }
21
+ column_names << attr.to_sym
22
+ end
23
+ end
24
+
25
+ ## ActiveRecord like APIs
26
+
14
27
  def primary_key
15
28
  "id"
16
29
  end
17
30
 
18
31
  def column_names
19
- raise NotImplementedError
32
+ @_column_names ||= [:id]
20
33
  end
21
34
 
22
35
  def all
23
- all_data.map {|data| new(data) }
36
+ data.map {|val| new(val) }
24
37
  end
25
38
 
26
- def find(*ids)
27
- ids = ids.first if ids.size == 1
28
- values = find_by_id(ids)
29
-
30
- case
31
- when values.empty? then nil
32
- when ids.is_a?(Array) then values.map {|val| new(val)}
33
- else new(values.first)
39
+ def find(ids)
40
+ if values = find_by_ids(ids).compact.presence
41
+ ids.is_a?(Array) ? values.map {|val| new(val) } : new(values.first)
42
+ else
43
+ raise RecordNotFound.new(self, id: ids)
34
44
  end
35
45
  end
36
46
 
37
- def find_by(params)
38
- params.symbolize_keys!
39
- if id = params.delete(:id)
40
- values = find_by_id(id)
41
- return nil if values.empty?
42
- return new(values.first) if params.empty?
43
-
44
- values.each {|val|
45
- record = new(val)
46
- return record if params.all? {|k,v| record.send(k) == v }
47
- }
47
+ def find_by(conditions)
48
+ if conditions.is_a?(Hash)
49
+ conditions.symbolize_keys!
50
+ if id = conditions.delete(:id)
51
+ values = find_by_ids(id).compact
52
+ case
53
+ when values.empty? then nil
54
+ when conditions.empty? then new(values.first)
55
+ else
56
+ value = values.detect {|val|
57
+ record = new(val)
58
+ conditions.all? {|k, v| record.public_send(k) == v }
59
+ }
60
+ value ? new(value) : nil
61
+ end
62
+ else
63
+ all.detect {|r|
64
+ conditions.all? {|k, v| r.public_send(k) == v }
65
+ }
66
+ end
48
67
  else
49
- all_data.each {|val|
50
- record = new(val)
51
- return record if params.all? {|k,v| record.send(k) == v }
52
- }
68
+ values = find_by_ids(conditions).compact
69
+ values.empty? ? nil : new(values.first)
53
70
  end
54
71
  end
55
72
 
56
- def where(params)
57
- all.select {|record| params.all? {|k,v| record.send(k) == v } }
73
+ def find_by!(conditions)
74
+ find_by(conditions.dup) || (raise RecordNotFound.new(self, conditions))
58
75
  end
59
76
 
60
- def exists?(record)
61
- if record.is_a?(self)
62
- _id = record.id
63
- return false unless _id
77
+ def where(conditions)
78
+ if id = conditions.delete(:id)
79
+ values = find_by_ids(id).compact
80
+ if conditions.empty?
81
+ values.map {|val| new(val) }
82
+ else
83
+ values.map {|val|
84
+ record = new(val)
85
+ conditions.all? {|k, v| record.public_send(k) == v } ? record : nil
86
+ }.compact
87
+ end
64
88
  else
65
- _id = record.to_i
89
+ all.select {|r|
90
+ conditions.all? {|k, v| r.public_send(k) == v }
91
+ }
66
92
  end
67
-
68
- redis.hexists data_key, _id
69
- end
70
-
71
- delegate :first, :last, to: :all
72
-
73
- def count
74
- redis.hlen(data_key)
75
- end
76
-
77
- def ids
78
- redis.hkeys(data_key).map(&:to_i)
79
93
  end
80
94
 
81
95
  def create(attrs = {})
@@ -83,53 +97,99 @@ module Findable
83
97
  record.save
84
98
  record
85
99
  end
100
+ alias_method :create!, :create
86
101
 
87
- def create!(attrs = {})
88
- record = new(attrs)
89
- record.save!
90
- record
102
+ [:first, :last].each do |m|
103
+ define_method(m) do
104
+ value = self.data.public_send(m)
105
+ value ? new(value) : nil
106
+ end
91
107
  end
92
108
 
93
- def delete_all
94
- redis.del(data_key)
95
- end
109
+ ## Query APIs
110
+
111
+ delegate :find_by_ids, :data, to: :query
112
+ delegate :count, :ids, :delete_all, to: :query
96
113
  alias_method :destroy_all, :delete_all
97
114
 
98
- def transaction(&block)
99
- redis.multi &block
115
+ def exists?(obj)
116
+ if _id = id_from(obj)
117
+ query.exists?(data_key, _id)
118
+ else
119
+ false
120
+ end
100
121
  end
101
122
 
102
- def import(records)
103
- data = records.each_with_object([]) {|record, obj|
104
- record.id ||= auto_incremented_id
105
- obj << record.id
106
- obj << record.to_json
107
- }
108
- redis.hmset(data_key, *data)
123
+ def insert(obj)
124
+ query.insert(obj.attributes)
109
125
  end
110
126
 
111
- def insert(record)
112
- record.id ||= auto_incremented_id
113
- redis.hset(data_key, record.id, record.to_json)
127
+ def delete(obj)
128
+ if _id = id_from(obj)
129
+ query.delete(_id)
130
+ end
114
131
  end
115
132
 
116
- def delete(id)
117
- redis.hdel(data_key, id)
133
+ def query
134
+ @_query ||= Query.new(self)
118
135
  end
119
136
 
120
137
  private
121
- def auto_incremented_id
122
- redis.hincrby(info_key, :auto_inclement, 1)
138
+ def id_from(obj)
139
+ obj.is_a?(self) ? obj.id : obj.to_i
123
140
  end
141
+ end
124
142
 
125
- def all_data
126
- redis.hvals(data_key)
127
- end
143
+ def initialize(params = {})
144
+ params = Oj.load(params) if params.is_a?(String)
145
+ params.symbolize_keys!
146
+ params.keys.each {|attr| self.class.define_field(attr) }
147
+ @_attributes = params
148
+ end
128
149
 
129
- def find_by_id(id)
130
- redis.hmget(data_key, *Array(id)).compact
131
- end
150
+ def id
151
+ attributes[:id].presence
152
+ end
153
+
154
+ def id=(value)
155
+ attributes[:id] = value
156
+ end
157
+
158
+ def hash
159
+ id.hash
160
+ end
161
+
162
+ def new_record?
163
+ id ? !self.class.exists?(self) : true
132
164
  end
165
+
166
+ def persisted?
167
+ !new_record?
168
+ end
169
+
170
+ def save
171
+ @_attributes = self.class.insert(self)
172
+ self
173
+ end
174
+ alias_method :save!, :save
175
+
176
+ def delete
177
+ self.class.delete(self)
178
+ end
179
+ alias_method :destroy, :delete
180
+
181
+ def attributes
182
+ @_attributes ||= {}
183
+ end
184
+
185
+ private
186
+ def attribute=(attr, value)
187
+ attributes[attr.to_sym] = value
188
+ end
189
+
190
+ def attribute?(attr)
191
+ attributes[attr.to_sym].present?
192
+ end
133
193
  end
134
194
  end
135
195