ruby-wpdb 1.0.beta1

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.
@@ -0,0 +1,32 @@
1
+ Sequel.inflections do |inflect|
2
+ # Unless we tell Sequel otherwise, it will try to inflect the singular
3
+ # of "commentmeta" using the "data" -> "datum" rule, leaving us with the
4
+ # bizarre "commentmetum".
5
+ inflect.uncountable 'commentmeta'
6
+ end
7
+
8
+ module WPDB
9
+ class Comment < Sequel::Model(:"#{WPDB.prefix}comments")
10
+ plugin :validation_helpers
11
+
12
+ many_to_one :post, :key => :comment_post_ID, :class => :'WPDB::Post'
13
+ one_to_many :commentmeta, :class => :'WPDB::CommentMeta'
14
+
15
+ def validate
16
+ super
17
+ validates_presence [:comment_author, :comment_author_email, :comment_date, :comment_date_gmt, :comment_parent, :comment_approved]
18
+ end
19
+
20
+ def before_validation
21
+ self.comment_date ||= Time.now
22
+ self.comment_date_gmt ||= Time.now.utc
23
+ self.comment_parent ||= 0
24
+ self.comment_approved ||= 1
25
+ super
26
+ end
27
+ end
28
+
29
+ class CommentMeta < Sequel::Model(:"#{WPDB.prefix}commentmeta")
30
+ many_to_one :comment, :class => :'WPDB::Comment'
31
+ end
32
+ end
@@ -0,0 +1,208 @@
1
+ require 'php_serialize'
2
+ require 'csv'
3
+
4
+ module WPDB
5
+ module GravityForms
6
+ class Form < Sequel::Model(:"#{WPDB.prefix}rg_form")
7
+ one_to_many :leads, :class => :'WPDB::GravityForms::Lead'
8
+
9
+ one_to_one :meta, :class => :'WPDB::GravityForms::FormMeta'
10
+
11
+ def fields
12
+ display_meta = PHP.unserialize(meta.display_meta)
13
+ display_meta['fields']
14
+ end
15
+
16
+ def field_name(number)
17
+ number = number.to_f
18
+ number = number.to_i if number.round == number
19
+ number = number.to_s
20
+
21
+ field = fields.find { |f| f['id'].to_s == number } || {}
22
+ field['label']
23
+ end
24
+
25
+ def to_csv(io)
26
+ io.puts(CSV.generate_line(leads.first.values.keys))
27
+
28
+ leads.each do |lead|
29
+ io.puts(lead.to_csv)
30
+ end
31
+
32
+ nil
33
+ end
34
+
35
+ class << self
36
+ def from_title(title)
37
+ self.first(:title => title)
38
+ end
39
+ end
40
+ end
41
+
42
+ class FormMeta < Sequel::Model(:"#{WPDB.prefix}rg_form_meta")
43
+ one_to_one :form, :class => :'WPDB::GravityForms::Form'
44
+ end
45
+
46
+ class Lead < Sequel::Model(:"#{WPDB.prefix}rg_lead")
47
+ many_to_one :form, :class => :'WPDB::GravityForms::Form'
48
+
49
+ one_to_many :details, :class => :'WPDB::GravityForms::Detail'
50
+
51
+ def values
52
+ details.each_with_object({}) do |detail, values|
53
+ key = form.field_name(detail.field_number) || detail.field_number
54
+ values[key] = detail.value
55
+ end
56
+ end
57
+
58
+ def to_csv
59
+ CSV.generate_line(values.values)
60
+ end
61
+ end
62
+
63
+ class Detail < Sequel::Model(:"#{WPDB.prefix}rg_lead_detail")
64
+ many_to_one :lead, :class => :'WPDB::GravityForms::Lead'
65
+
66
+ one_to_one :long_value, :key => :lead_detail_id, :class => :'WPDB::GravityForms::DetailLong'
67
+ end
68
+
69
+ class DetailLong < Sequel::Model(:"#{WPDB.prefix}rg_lead_detail_long")
70
+ one_to_one :lead_detail, :class => :'WPDB::GravityForms::DetailLong'
71
+ end
72
+
73
+ # For each form that we have, define a class for accessing its
74
+ # leads. So, if you have a form called "User Registration", we'll
75
+ # define a class called UserRegistration, allowing you to do
76
+ # things like:
77
+ #
78
+ # WPDB::GravityForms::UserRegistration.where(:date_registered => '2013-01-01').each do { |l| puts "#{l.first_name} #{l.last_name}" }
79
+ #
80
+ # All fields in the form are available as accessors.
81
+ class ModelGenerator
82
+ attr_reader :models
83
+
84
+ def initialize(forms = nil)
85
+ @forms = Array(forms) || Form.all
86
+ @models = []
87
+ end
88
+
89
+ def generate
90
+ @forms.each do |form|
91
+ form_name = WPDB.camelize(form.title)
92
+ form_class = build_class(form)
93
+
94
+ @models << WPDB::GravityForms.const_set(form_name, form_class)
95
+ end
96
+ end
97
+
98
+ private
99
+
100
+ # Constructs a new Ruby class, inherited from Sequel::Model, that
101
+ # will hold our model-like functionality. Sequel gives us most of
102
+ # this for free, by virtue of defining a dataset.
103
+ def build_class(form)
104
+ @labels = []
105
+
106
+ dataset = WPDB.db[:"#{WPDB.prefix}rg_lead___l"]
107
+ .where(:"l__form_id" => form.id)
108
+
109
+ dataset = join_fields(dataset, form.fields)
110
+ dataset = dataset.select_all(:l)
111
+ dataset = select_fields(dataset, form.fields)
112
+ dataset = dataset.from_self
113
+
114
+ Class.new(Sequel::Model) do
115
+ set_dataset dataset
116
+ end
117
+ end
118
+
119
+ def sanitise_label(original_label)
120
+ original_label = original_label.to_s
121
+ return unless original_label.length > 0
122
+
123
+ i = 1
124
+
125
+ label = WPDB.underscoreize(original_label)
126
+ while @labels.include?(label)
127
+ label = WPDB.underscoreize(original_label + i.to_s)
128
+ i += 1
129
+ end
130
+
131
+ @labels << label
132
+
133
+ label
134
+ end
135
+
136
+ def ignored_fields
137
+ ["html_block"]
138
+ end
139
+
140
+ def join_fields(dataset, fields)
141
+ fields.each_with_index do |field, i|
142
+ next unless field && field['label']
143
+ next if ignored_fields.include?(field['label'])
144
+
145
+ dataset = dataset.join_table(
146
+ :left,
147
+ :"#{WPDB.prefix}rg_lead_detail___ld#{i}",
148
+ { :field_number => field['id'], :lead_id => :"l__id" }
149
+ )
150
+ end
151
+
152
+ dataset
153
+ end
154
+
155
+ def select_fields(dataset, fields)
156
+ fields.each_with_index do |field, i|
157
+ next unless field && field['label']
158
+ next if ignored_fields.include?(field['label'])
159
+
160
+ field_name = sanitise_label(field['label'])
161
+
162
+ next if field_name.blank?
163
+
164
+ dataset = dataset.select_append(:"ld#{i}__value___#{field_name}")
165
+ end
166
+
167
+ dataset
168
+ end
169
+ end
170
+
171
+ # When a request is made to, for example,
172
+ # WPDB::GravityForms::SomeClass, this method will fire. If there's
173
+ # a GravityForm whose title, when camelised, is "SomeClass", a model
174
+ # will be created for that form.
175
+ #
176
+ # After the first time, the constant for that form will have been
177
+ # created, and so this hook will no longer fire.
178
+ def self.const_missing(name)
179
+ Form.each do |form|
180
+ if name.to_s == WPDB.camelize(form.title)
181
+ ModelGenerator.new(form).generate
182
+ return WPDB::GravityForms.const_get(name)
183
+ end
184
+ end
185
+
186
+ raise "Form not found: #{name}"
187
+ end
188
+ end
189
+
190
+ # Given a string, will convert it to a camel case suitable for use in
191
+ # a Ruby constant (which means no non-alphanumeric characters and no
192
+ # leading numbers).
193
+ def self.camelize(string)
194
+ string.gsub(/[^a-z0-9 ]/i, '')
195
+ .gsub(/^[0-9]+/, '')
196
+ .split(/\s+/)
197
+ .map { |t| t.strip.capitalize }
198
+ .join('')
199
+ end
200
+
201
+ # Given a string, will convert it an_underscored_value suitable for
202
+ # use in a Ruby variable name/symbol.
203
+ def self.underscoreize(string)
204
+ string.downcase
205
+ .gsub(' ', '_')
206
+ .gsub(/[^a-z0-9_]/, '')
207
+ end
208
+ end
@@ -0,0 +1,8 @@
1
+ module WPDB
2
+ class Link < Sequel::Model(:"#{WPDB.prefix}links")
3
+ include Termable
4
+
5
+ one_to_many :termrelationships, :key => :object_id, :key_method => :obj_id, :class => 'WPDB::TermRelationship'
6
+ many_to_many :termtaxonomy, :left_key => :object_id, :right_key => :term_taxonomy_id, :join_table => :"#{WPDB.prefix}term_relationships", :class => 'WPDB::TermTaxonomy'
7
+ end
8
+ end
@@ -0,0 +1,24 @@
1
+ module WPDB
2
+ class Option < Sequel::Model(:"#{WPDB.prefix}options")
3
+ plugin :validation_helpers
4
+
5
+ def validate
6
+ super
7
+ validates_presence [:option_name]
8
+ end
9
+
10
+ def before_validation
11
+ self.autoload ||= "yes"
12
+ super
13
+ end
14
+
15
+ # Given the name of an option, will return the option value for that
16
+ # option - or nil of no option with that name exists.
17
+ #
18
+ # @param [String] name The option_name to fetch
19
+ # @return String
20
+ def self.get_option(name)
21
+ Option.where(:option_name => name).get(:option_value)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,67 @@
1
+ Sequel.inflections do |inflect|
2
+ # Unless we tell Sequel otherwise, it will try to inflect the singular
3
+ # of "postmeta" using the "data" -> "datum" rule, leaving us with the
4
+ # bizarre "postmetum".
5
+ inflect.uncountable 'postmeta'
6
+ end
7
+
8
+ module WPDB
9
+ class Post < Sequel::Model(:"#{WPDB.prefix}posts")
10
+ include Termable
11
+
12
+ plugin :validation_helpers
13
+ plugin :sluggable, :source => :post_title, :target => :post_name
14
+
15
+ many_to_one :parent, :class => self, :key => :post_parent
16
+ one_to_many :children,
17
+ :key => :post_parent,
18
+ :class => self do |ds|
19
+ ds.where(:post_type => ['attachment', 'revision']).invert
20
+ .where(:post_parent => self.ID)
21
+ end
22
+ one_to_many :revisions,
23
+ :key => :post_parent,
24
+ :class => self,
25
+ :conditions => { :post_type => 'revision' }
26
+ one_to_many :attachments,
27
+ :key => :post_parent,
28
+ :class => self,
29
+ :conditions => { :post_type => 'attachment' }
30
+
31
+ one_to_many :postmeta, :class => :'WPDB::PostMeta'
32
+ many_to_one :author, :key => :post_author, :class => :'WPDB::User'
33
+ one_to_many :comments, :key => :comment_post_ID, :class => :'WPDB::Comment'
34
+
35
+ one_to_many :termrelationships,
36
+ :key => :object_id,
37
+ :key_method => :obj_id,
38
+ :class => 'WPDB::TermRelationship'
39
+ many_to_many :termtaxonomy,
40
+ :left_key => :object_id,
41
+ :right_key => :term_taxonomy_id,
42
+ :join_table => :"#{WPDB.prefix}term_relationships",
43
+ :class => 'WPDB::TermTaxonomy'
44
+
45
+ def validate
46
+ super
47
+ validates_presence [:post_title, :post_type, :post_status]
48
+ validates_unique :post_name
49
+ end
50
+
51
+ def before_validation
52
+ self.post_type ||= "post"
53
+ self.post_status ||= "draft"
54
+ self.post_parent ||= 0
55
+ self.menu_order ||= 0
56
+ self.comment_status ||= "open"
57
+ self.ping_status ||= WPDB::Option.get_option("default_ping_status")
58
+ self.post_date ||= Time.now
59
+ self.post_date_gmt ||= Time.now.utc
60
+ super
61
+ end
62
+ end
63
+
64
+ class PostMeta < Sequel::Model(:"#{WPDB.prefix}postmeta")
65
+ many_to_one :posts, :class => :'WPDB::Post'
66
+ end
67
+ end
@@ -0,0 +1,49 @@
1
+ module WPDB
2
+ class Term < Sequel::Model(:"#{WPDB.prefix}terms")
3
+ plugin :validation_helpers
4
+ plugin :sluggable, :source => :name, :target => :slug
5
+
6
+ one_to_many :termtaxonomies, :class => 'WPDB::TermTaxonomy'
7
+
8
+ def validate
9
+ super
10
+ validates_presence :name
11
+ end
12
+ end
13
+
14
+ class TermTaxonomy < Sequel::Model(:"#{WPDB.prefix}term_taxonomy")
15
+ many_to_one :terms, :class => 'WPDB::Term'
16
+ one_to_many :termrelationships, :class => 'WPDB::TermRelationship'
17
+ many_to_many :posts, :left_key => :term_taxonomy_id, :right_key => :object_id, :join_table => :"#{WPDB.prefix}term_relationships", :class => 'WPDB::Post'
18
+ many_to_many :links, :left_key => :term_taxonomy_id, :right_key => :object_id, :join_table => :"#{WPDB.prefix}term_relationships", :class => 'WPDB::Link'
19
+ end
20
+
21
+ class TermRelationship < Sequel::Model(:"#{WPDB.prefix}term_relationships")
22
+ def_column_alias(:obj_id, :object_id)
23
+
24
+ many_to_one :termtaxonomy, :class => 'WPDB::TermTaxonomy'
25
+ many_to_one :posts, :class => 'WPDB::Post'
26
+ many_to_one :links, :class => 'WPDB::Link'
27
+ end
28
+
29
+ module Termable
30
+ # For objects that have a relationship with termtaxonomies, this
31
+ # module can be mixed in and gives the ability to add a term
32
+ # directly to the model, rather than creating the relationship
33
+ # yourself. Used by Post and Link.
34
+ def add_term(term, taxonomy)
35
+ if term.respond_to?(:term_id)
36
+ term_id = term.term_id
37
+ else
38
+ term_id = term.to_i
39
+ end
40
+
41
+ term_taxonomy = WPDB::TermTaxonomy.where(:term_id => term_id, :taxonomy => taxonomy).first
42
+ unless term_taxonomy
43
+ term_taxonomy = WPDB::TermTaxonomy.create(:term_id => term_id, :taxonomy => taxonomy)
44
+ end
45
+
46
+ add_termtaxonomy(term_taxonomy)
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,39 @@
1
+ require 'digest/md5'
2
+
3
+ Sequel.inflections do |inflect|
4
+ # Unless we tell Sequel otherwise, it will try to inflect the singular
5
+ # of "usermeta" using the "data" -> "datum" rule, leaving us with the
6
+ # bizarre "usermetum".
7
+ inflect.uncountable 'usermeta'
8
+ end
9
+
10
+ module WPDB
11
+ class User < Sequel::Model(:"#{WPDB.user_prefix}users")
12
+ plugin :validation_helpers
13
+
14
+ one_to_many :usermeta, :class => :'WPDB::UserMeta'
15
+ one_to_many :posts, :key => :post_author, :class => :'WPDB::Post'
16
+
17
+ def validate
18
+ super
19
+ validates_presence [:user_login, :user_pass, :user_email, :user_registered]
20
+ validates_unique :user_login, :user_email
21
+ end
22
+
23
+ def before_validation
24
+ self.user_registered ||= Time.now
25
+
26
+ # If the password we've been given isn't a hash, then MD5 it.
27
+ # Although WordPress no longer uses MD5 hashes, it will update
28
+ # them on successful login, so we're ok to create them here.
29
+ unless user_pass =~ /\$[A-Z]\$/ || user_pass =~ /[a-z0-9]{32}/
30
+ self.user_pass = Digest::MD5.hexdigest(user_pass.to_s)
31
+ end
32
+
33
+ super
34
+ end
35
+ end
36
+
37
+ class UserMeta < Sequel::Model(:"#{WPDB.user_prefix}usermeta")
38
+ end
39
+ end
@@ -0,0 +1,3 @@
1
+ module WPDB
2
+ VERSION = "1.0.beta1"
3
+ end
data/lib/ruby-wpdb.rb ADDED
@@ -0,0 +1,51 @@
1
+ require 'bundler'
2
+ Bundler.setup
3
+
4
+ require 'sequel'
5
+ require 'pry'
6
+ require 'pry-debugger'
7
+
8
+ module WPDB
9
+ class << self
10
+ attr_accessor :db, :prefix, :user_prefix
11
+
12
+ # Given the path to a YAML file, will initialise WPDB using the
13
+ # config files found in that file.
14
+ def from_config(file = nil)
15
+ file ||= File.dirname(__FILE__) + '/../config.yml'
16
+ config = YAML::load_file(file)
17
+
18
+ uri = 'mysql2://'
19
+ uri += "#{config['username']}:#{config['password']}"
20
+ uri += "@#{config['hostname']}"
21
+ uri += ":#{config['port']}" if config['port']
22
+ uri += "/#{config['database']}"
23
+
24
+ init(uri, config['prefix'])
25
+ end
26
+
27
+ # Initialises Sequel, sets up some necessary variables (like
28
+ # WordPress's table prefix), and then includes our models.
29
+ #
30
+ # @param [String] A database connection uri, e.g.
31
+ # mysql2://user:pass@host:port/dbname
32
+ # @param [String] The database table prefix used by the install of
33
+ # WordPress you'll be querying. Defaults to wp_
34
+ # @param [String] The prefix of the users table; if not specified,
35
+ # the general database table prefix will be used.
36
+ def init(uri, prefix = nil, user_prefix = nil)
37
+ WPDB.db = Sequel.connect(uri)
38
+ WPDB.prefix = prefix || 'wp_'
39
+ WPDB.user_prefix = user_prefix || WPDB.prefix
40
+
41
+ require_relative 'ruby-wpdb/options'
42
+ require_relative 'ruby-wpdb/users'
43
+ require_relative 'ruby-wpdb/terms'
44
+ require_relative 'ruby-wpdb/posts'
45
+ require_relative 'ruby-wpdb/comments'
46
+ require_relative 'ruby-wpdb/links'
47
+ require_relative 'ruby-wpdb/gravityforms'
48
+ end
49
+ end
50
+ end
51
+
@@ -0,0 +1,40 @@
1
+ require_relative 'test_helper'
2
+
3
+ describe WPDB::Comment do
4
+ before do
5
+ @comment = WPDB::Comment.create(
6
+ :comment_author => 'Testy Testerson',
7
+ :comment_author_email => 'testy@example.com',
8
+ :comment_content => 'Test'
9
+ )
10
+ end
11
+
12
+ it "creates a new comment" do
13
+ assert @comment.comment_ID
14
+ end
15
+
16
+ it "attaches comments to posts" do
17
+ post = WPDB::Post.create(:post_title => 'test', :post_author => 1)
18
+ assert post.ID
19
+
20
+ post.add_comment(@comment)
21
+ assert @comment.post
22
+ assert post.comments.length
23
+ assert_equal post.comments.first.comment_post_ID, post.ID
24
+
25
+ post.destroy
26
+ end
27
+
28
+ it "adds commentmeta" do
29
+ @comment.add_commentmeta(:meta_key => 'test', :meta_value => 'test')
30
+
31
+ comment = WPDB::Comment.where(:comment_ID => @comment.comment_ID).first
32
+ assert comment.commentmeta.length > 0
33
+ assert_equal 'test', comment.commentmeta.first.meta_key
34
+ assert_equal 'test', comment.commentmeta.first.meta_value
35
+ end
36
+
37
+ after do
38
+ @comment.destroy
39
+ end
40
+ end
@@ -0,0 +1,25 @@
1
+ require_relative 'test_helper'
2
+
3
+ describe WPDB::GravityForms do
4
+ before do
5
+ @form = WPDB::GravityForms::Form.first
6
+ @class_name = WPDB.camelize(@form.title).to_sym
7
+ end
8
+
9
+ it "fetches a form" do
10
+ assert @form.id
11
+ end
12
+
13
+ it "associates leads with a form" do
14
+ assert @form.leads.length
15
+ end
16
+
17
+ it "associates lead detail with a lead" do
18
+ assert @form.leads.first.details.length
19
+ end
20
+
21
+ it "lazily loads models for forms" do
22
+ klass = WPDB::GravityForms.const_get(@class_name)
23
+ assert_equal Class, klass.class
24
+ end
25
+ end
@@ -0,0 +1,28 @@
1
+ require_relative 'test_helper'
2
+
3
+ describe WPDB::Link do
4
+ before do
5
+ @link = WPDB::Link.create(:link_url => 'http://example.com', :link_name => 'Example')
6
+ end
7
+
8
+ it "creates links" do
9
+ assert @link.link_id
10
+ end
11
+
12
+ it "can attach terms to links" do
13
+ term = WPDB::Term.create(:name => 'terming links')
14
+ @link.add_term(term, 'category')
15
+ @link.save
16
+
17
+ assert @link.link_id
18
+ assert @link.termtaxonomy.length
19
+ assert_equal term.term_id, @link.termtaxonomy.first.term_id
20
+ assert_equal 'category', @link.termtaxonomy.first.taxonomy
21
+
22
+ term.destroy
23
+ end
24
+
25
+ after do
26
+ @link.destroy
27
+ end
28
+ end
@@ -0,0 +1,26 @@
1
+ require_relative 'test_helper'
2
+
3
+ describe WPDB::Option do
4
+ before do
5
+ @option = WPDB::Option.create(:option_name => 'test', :option_value => 'test')
6
+ end
7
+
8
+ it "creates options" do
9
+ assert @option.option_id
10
+ end
11
+
12
+ it "enforces the uniqueness of option names" do
13
+ assert_raises Sequel::UniqueConstraintViolation do
14
+ WPDB::Option.create(:option_name => 'test', :option_value => 'test')
15
+ end
16
+ end
17
+
18
+ it "has a shorthand for fetching options" do
19
+ assert_equal 'test', WPDB::Option.get_option('test')
20
+ assert_equal nil, WPDB::Option.get_option('non-existent-key')
21
+ end
22
+
23
+ after do
24
+ @option.destroy
25
+ end
26
+ end
@@ -0,0 +1,50 @@
1
+ require_relative 'test_helper'
2
+
3
+ describe WPDB::Post do
4
+ before do
5
+ @post = WPDB::Post.create(:post_title => 'Hello world', :post_author => 1)
6
+ end
7
+
8
+ it "creates a new post" do
9
+ assert @post.ID
10
+ end
11
+
12
+ it "fetches all posts" do
13
+ assert WPDB::Post.all
14
+ end
15
+
16
+ it "manages postmeta" do
17
+ @post.add_postmeta(:meta_key => 'test', :meta_value => 'test')
18
+ @post.save
19
+
20
+ meta_value = @post.postmeta.first.meta_value
21
+ assert_equal 'test', meta_value
22
+ end
23
+
24
+ it "manages the hierarchy of posts" do
25
+ @post.add_child(WPDB::Post.create(:post_title => 'Child', :post_author => 1))
26
+ @post.save
27
+
28
+ assert_equal 'Child', @post.children.first.post_title
29
+
30
+ @post.children.first.destroy
31
+ end
32
+
33
+ it "fetches revisions of posts" do
34
+ revision = WPDB::Post.create(:post_type => 'revision', :post_title => 'Revision', :post_parent => @post.ID, :post_author => 1)
35
+ assert_equal 'Revision', @post.revisions.first.post_title
36
+
37
+ revision.destroy
38
+ end
39
+
40
+ it "fetches attachments to posts" do
41
+ attachment = WPDB::Post.create(:post_type => 'attachment', :post_title => 'Attachment', :post_parent => @post.ID, :post_author => 1)
42
+ assert_equal 'Attachment', @post.attachments.first.post_title
43
+
44
+ attachment.destroy
45
+ end
46
+
47
+ after do
48
+ @post.destroy
49
+ end
50
+ end