ruby-wpdb 1.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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