├── .gitignore ├── CHANGELOG ├── Gemfile ├── Guardfile ├── LICENSE ├── README.md ├── Rakefile ├── bin └── ruby-wpdb ├── data └── .gitignore ├── lib ├── ruby-wpdb.rb └── ruby-wpdb │ ├── comment.rb │ ├── commentmeta.rb │ ├── config.rb │ ├── gravityforms.rb │ ├── link.rb │ ├── option.rb │ ├── post.rb │ ├── postmeta.rb │ ├── term.rb │ ├── term_relationship.rb │ ├── term_taxonomy.rb │ ├── user.rb │ ├── usermeta.rb │ └── version.rb ├── ruby-wpdb.gemspec └── spec ├── lib ├── ruby-wpdb │ ├── comment_spec.rb │ ├── config_spec.rb │ ├── gravityforms_spec.rb │ ├── link_spec.rb │ ├── option_spec.rb │ ├── post_spec.rb │ ├── readme_spec.rb │ ├── term_spec.rb │ └── user_spec.rb └── ruby-wpdb_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | config.yml 2 | Gemfile.lock 3 | coverage 4 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | 1.2 2 | 3 | Rob Miller (1): 4 | Parse wp-config.php files as well as YAML config files 5 | 6 | 1.1.1 7 | 8 | Rob Miller (3): 9 | Fix: Remove pry-debugger dependency 10 | Fix: Add rubygems-tasks as a development dependency 11 | Update dependencies 12 | 13 | 1.1 14 | 15 | Rob Miller (2): 16 | Interactive console 17 | Bug fix: typo in README 18 | 19 | 1.0 20 | 21 | Rob Miller: 22 | Initial release 23 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | gem 'php-serialize', '~> 1.1', require: 'php_serialize' 5 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | guard :rspec do 2 | watch(%r{^spec/.+_spec\.rb$}) 3 | watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" } 4 | watch('spec/spec_helper.rb') { "spec" } 5 | end 6 | 7 | guard :bundler do 8 | watch('Gemfile') 9 | watch(%r{gemspec$}) 10 | end 11 | 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Rob Miller 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ruby-wpdb 2 | 3 | WordPress is enormously popular; it powers some 20% of the web. Rare is 4 | the web developer who doesn't come into contact with it, even those who 5 | develop primarily in other languages. It's user-friendly, server 6 | specification-friendly, and ubiquitous. 7 | 8 | But what if you need to interact with WordPress from the outside? It 9 | might be a cron-job, it might be a little command-line tool; you might 10 | want to export or import some content, or you might want to clean some 11 | things up. You don't have to do that in... *PHP*, do you? 12 | 13 | If you'd like to do it in Ruby instead, then this library might help 14 | with some of the boilerplate. 15 | 16 | It's a wrapper for the WordPress database, using 17 | [Sequel](http://sequel.rubyforge.org/), that gives you access to the 18 | WordPress database and all its content via a nice ORM. 19 | 20 | ## Installation 21 | 22 | You can install ruby-wpdb via RubyGems: 23 | 24 | $ gem install ruby-wpdb 25 | 26 | ## Usage 27 | 28 | [An in-depth tutorial for ruby-wpdb can be found 29 | here](http://robm.me.uk/ruby/2013/10/24/ruby-wpdb-part-1.html). 30 | 31 | But, in brief: 32 | 33 | With ruby-wpdb you can do simple things like get the five most recent posts: 34 | 35 | WPDB::Post.reverse_order(:post_date).limit(5).all 36 | 37 | Or more complicated things, like get the value of the custom field with 38 | the key "image", but only for the first post whose title consists solely 39 | of letters: 40 | 41 | WPDB::Post.first(:post_title => /^[a-z]+$/) 42 | .postmeta_dataset.first(:meta_key => 'image') 43 | .meta_value 44 | 45 | Of course, you're not limited to retrieving records, you can create them 46 | too: 47 | 48 | post = WPDB::Post.create(:post_title => 'Test', :post_content => 'Testing, testing, 123') 49 | 50 | And ruby-wpdb knows all about the relationship between things in 51 | WordPress — so if you want to create a new user, a post by that user, 52 | and a tag for that post, you can do so by referring to the objects alone 53 | without needing to know or care about what the actual relationships are 54 | from the perspective of the database: 55 | 56 | author = WPDB::User.create( 57 | :user_login => 'fred', 58 | :user_email => 'fred@example.com' 59 | ) 60 | 61 | term = WPDB::Term.create(:name => 'Fred Stuff') 62 | 63 | post = WPDB::Post.create( 64 | :post_title => 'Hello from Fred', 65 | :post_content => 'Hello, world', 66 | :author => author 67 | ).add_term(term, 'tag') 68 | 69 | ## GravityForms 70 | 71 | GravityForms is a great system for easily creating forms to capture 72 | data. But its flexibility can make querying and exporting entries 73 | difficult; querying even just a single form can often result in having 74 | to write hairy SQL queries with many self-joins. 75 | 76 | ruby-wpdb gives you an easier insight into your forms, allowing you to 77 | treat them as though they were models of any other kind. 78 | 79 | To make this a bit more concrete, imagine you had a contact form on your 80 | site called "Contact Form". It has four fields: "Name", "Email", 81 | "Message", and "Enquiry type". 82 | 83 | ruby-wpdb allows you to do things like get the latest five entries to 84 | have selected "quote" as their enquiry type: 85 | 86 | WPDB::GravityForms.ContactForm.where(:enquiry_type => 'quote').reverse_order(:date_created).limit(5).all 87 | 88 | Or display the messages that have been sent since the start of 2013: 89 | 90 | WPDB::GravityForms.ContactForm.where(:date_created >= Date.new(2013, 1, 1)).each do |entry| 91 | puts "#{entry.enquiry_type} enquiry from #{entry.name} <#{entry.email}>\n" 92 | puts entry.message 93 | puts "---" 94 | end 95 | 96 | Note that you get access to all the fields of the GravityForm as though 97 | they were first-class members of an actual model, allowing you to use 98 | their values when filtering and ordering. 99 | 100 | ## Console 101 | 102 | ruby-wpdb comes with an interactive REPL console, so you can explore 103 | a WordPress install without actually having to write any scripts. 104 | 105 | Fire it up with: 106 | 107 | $ ruby-wpdb console 108 | 109 | And you can then call: 110 | 111 | > init('mysql2://user:password@hostname/db_name') 112 | 113 | To connect to your database. 114 | 115 | If you find yourself connecting regularly to the same database, you can 116 | tell ruby-wpdb where to find a config file: 117 | 118 | $ ruby-wpdb console --config-file wp-config.php 119 | 120 | Which will remove the need to call `init` manually. 121 | 122 | Once you're in the console, you can do anything you'd be able to do with 123 | ruby-wpdb normally. So to list all posts with the title 'Hello World', 124 | you could call: 125 | 126 | > Post.where(post_title: "Hello World").all 127 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "rspec/core/rake_task" 2 | require 'rubygems/tasks' 3 | 4 | task :default => [:spec] 5 | 6 | desc "Run all specs" 7 | RSpec::Core::RakeTask.new do |t| 8 | t.pattern = "spec/**/*_spec.rb" 9 | t.rspec_opts = "-b" 10 | end 11 | 12 | desc "Run mutation tests" 13 | task :mutant do 14 | system "mutant --fail-fast --include lib --require ruby-wpdb --use rspec '::WPDB*'" 15 | end 16 | 17 | Gem::Tasks.new 18 | -------------------------------------------------------------------------------- /bin/ruby-wpdb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | 5 | require "pry" 6 | require "thor" 7 | 8 | require "ruby-wpdb" 9 | 10 | module WPDB 11 | class CLI < Thor 12 | package_name "ruby-wpdb" 13 | 14 | method_option :config_file, required: false 15 | 16 | desc "console", "Runs an interactive ruby-wpdb console" 17 | def console 18 | begin 19 | WPDB.from_config(options[:config_file]) 20 | rescue WPDB::ConfigFileError 21 | puts "I couldn't find a config file, so you'll need to specify one manually." 22 | end 23 | 24 | Pry.config.should_load_rc = false 25 | Pry.config.prompt = [ 26 | proc { |_obj, nest_level, _| "ruby-wpdb: #{nest_level} > " }, 27 | proc { |_obj, nest_level, _| "ruby-wpdb: #{nest_level}*> " }, 28 | ] 29 | 30 | puts "You're now in an interactive ruby-wpdb session. To leave, type 'exit'" 31 | 32 | if WPDB.initialized 33 | puts "WPDB is already initialized; you're connected to #{WPDB.db.uri}" 34 | else 35 | puts "WPDB isn't yet connected to a WordPress install; use from_config() or init() to initialize it." 36 | end 37 | 38 | puts "\n" 39 | 40 | WPDB.pry 41 | end 42 | end 43 | end 44 | 45 | WPDB::CLI.start 46 | -------------------------------------------------------------------------------- /data/.gitignore: -------------------------------------------------------------------------------- 1 | query.log 2 | wp-config-sample.php 3 | -------------------------------------------------------------------------------- /lib/ruby-wpdb.rb: -------------------------------------------------------------------------------- 1 | require 'bundler/setup' 2 | 3 | require 'sequel' 4 | require 'pry' 5 | require 'pathname' 6 | 7 | require_relative 'ruby-wpdb/config' 8 | 9 | module WPDB 10 | class ConfigFileError < StandardError; end 11 | 12 | class << self 13 | attr_accessor :db, :prefix, :user_prefix, :initialized 14 | 15 | # Given the path to a YAML file, will initialise WPDB using the 16 | # config files found in that file. 17 | def from_config(file = nil) 18 | config_file = config_file(file) 19 | 20 | init(config_file.config[:uri], config_file.config[:prefix]) 21 | end 22 | 23 | def config_file(file = nil) 24 | file = Config::AutoDiscover.new.file unless file 25 | raise ConfigFileError, "No config file specified, and none found" unless file 26 | 27 | file = Config::AutoFormat.new(file) 28 | raise ConfigFileError, "Unknown config file format for file #{file}" unless file.format 29 | 30 | file 31 | end 32 | 33 | # Initialises Sequel, sets up some necessary variables (like 34 | # WordPress's table prefix), and then includes our models. 35 | # 36 | # @param [String] A database connection uri, e.g. 37 | # mysql2://user:pass@host:port/dbname 38 | # @param [String] The database table prefix used by the install of 39 | # WordPress you'll be querying. Defaults to wp_ 40 | # @param [String] The prefix of the users table; if not specified, 41 | # the general database table prefix will be used. 42 | def init(uri, prefix = nil, user_prefix = nil) 43 | WPDB.db = Sequel.connect(uri) 44 | WPDB.prefix = prefix || 'wp_' 45 | WPDB.user_prefix = user_prefix || WPDB.prefix 46 | 47 | require_relative 'ruby-wpdb/option' 48 | require_relative 'ruby-wpdb/user' 49 | require_relative 'ruby-wpdb/usermeta' 50 | require_relative 'ruby-wpdb/term' 51 | require_relative 'ruby-wpdb/term_relationship' 52 | require_relative 'ruby-wpdb/term_taxonomy' 53 | require_relative 'ruby-wpdb/post' 54 | require_relative 'ruby-wpdb/postmeta' 55 | require_relative 'ruby-wpdb/comment' 56 | require_relative 'ruby-wpdb/commentmeta' 57 | require_relative 'ruby-wpdb/link' 58 | require_relative 'ruby-wpdb/gravityforms' 59 | 60 | WPDB.initialized = true 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /lib/ruby-wpdb/comment.rb: -------------------------------------------------------------------------------- 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 | end 29 | -------------------------------------------------------------------------------- /lib/ruby-wpdb/commentmeta.rb: -------------------------------------------------------------------------------- 1 | module WPDB 2 | class CommentMeta < Sequel::Model(:"#{WPDB.prefix}commentmeta") 3 | many_to_one :comment, class: :'WPDB::Comment' 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /lib/ruby-wpdb/config.rb: -------------------------------------------------------------------------------- 1 | require "yaml" 2 | require "erb" 3 | 4 | module WPDB 5 | module Config 6 | module ConfigFormat 7 | def initialize(file) 8 | if file.respond_to? :read 9 | @contents = file.read 10 | else 11 | @contents = File.read(file) 12 | end 13 | 14 | parse 15 | end 16 | 17 | def config 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 | { uri: uri, prefix: @config['prefix'] } 25 | end 26 | end 27 | 28 | class YAML 29 | include ConfigFormat 30 | 31 | def parse 32 | @config = ::YAML::load(ERB.new(@contents).result) 33 | end 34 | end 35 | 36 | class WPConfig 37 | include ConfigFormat 38 | 39 | def parse 40 | config = Hash[@contents.scan(/define\((?:'|")(.+)(?:'|"), *(?:'|")(.+)(?:'|")\)/)] 41 | @config = { 42 | "username" => config["DB_USER"], 43 | "password" => config["DB_PASSWORD"], 44 | "hostname" => config["DB_HOST"], 45 | "database" => config["DB_NAME"], 46 | "prefix" => config["DB_PREFIX"] || "wp_" 47 | } 48 | end 49 | end 50 | 51 | class AutoDiscover 52 | def initialize(directory = Dir.pwd) 53 | @directory = Pathname(directory) 54 | end 55 | 56 | def file 57 | @file ||= files.map { |file| @directory + file }.find { |file| File.exist?(file) } 58 | end 59 | 60 | private 61 | def files 62 | ["wp-config.php", "../wp-config.php", "wp/wp-config.php", "wordpress/wp-config.php", "config.yml"] 63 | end 64 | end 65 | 66 | class AutoFormat 67 | attr_reader :filename 68 | 69 | def initialize(filename) 70 | @filename = Pathname(filename) 71 | end 72 | 73 | def format 74 | case filename.extname 75 | when ".yml", ".yaml" 76 | Config::YAML 77 | when ".php" 78 | Config::WPConfig 79 | else 80 | nil 81 | end 82 | end 83 | 84 | def config 85 | format.new(filename).config 86 | end 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /lib/ruby-wpdb/gravityforms.rb: -------------------------------------------------------------------------------- 1 | require 'php_serialize' 2 | require 'json' 3 | require 'csv' 4 | 5 | module WPDB 6 | module GravityForms 7 | class Form < Sequel::Model(:"#{WPDB.prefix}rg_form") 8 | one_to_many :leads, :class => :'WPDB::GravityForms::Lead' 9 | 10 | one_to_one :meta, :class => :'WPDB::GravityForms::FormMeta' 11 | 12 | def fields 13 | begin 14 | display_meta = PHP.unserialize(meta.display_meta) 15 | rescue TypeError 16 | display_meta = JSON.parse(meta.display_meta) 17 | end 18 | display_meta['fields'] 19 | end 20 | 21 | def field_name(number) 22 | number = number.to_f 23 | number = number.to_i if number.round == number 24 | number = number.to_s 25 | 26 | field = fields.find { |f| f['id'].to_s == number } || {} 27 | field['label'] 28 | end 29 | 30 | def to_csv(io) 31 | io.puts(CSV.generate_line(leads.first.values.keys)) 32 | 33 | leads.each do |lead| 34 | io.puts(lead.to_csv) 35 | end 36 | 37 | nil 38 | end 39 | 40 | class << self 41 | def from_title(title) 42 | self.first(:title => title) 43 | end 44 | end 45 | end 46 | 47 | class FormMeta < Sequel::Model(:"#{WPDB.prefix}rg_form_meta") 48 | one_to_one :form, :class => :'WPDB::GravityForms::Form' 49 | end 50 | 51 | class Lead < Sequel::Model(:"#{WPDB.prefix}rg_lead") 52 | many_to_one :form, :class => :'WPDB::GravityForms::Form' 53 | 54 | one_to_many :details, :class => :'WPDB::GravityForms::Detail' 55 | 56 | def values 57 | details.each_with_object({}) do |detail, values| 58 | key = form.field_name(detail.field_number) || detail.field_number 59 | values[key] = detail.value 60 | end 61 | end 62 | 63 | def to_csv 64 | CSV.generate_line(values.values) 65 | end 66 | end 67 | 68 | class Detail < Sequel::Model(:"#{WPDB.prefix}rg_lead_detail") 69 | many_to_one :lead, :class => :'WPDB::GravityForms::Lead' 70 | 71 | one_to_one :long_value, :key => :lead_detail_id, :class => :'WPDB::GravityForms::DetailLong' 72 | end 73 | 74 | class DetailLong < Sequel::Model(:"#{WPDB.prefix}rg_lead_detail_long") 75 | one_to_one :lead_detail, :class => :'WPDB::GravityForms::DetailLong' 76 | end 77 | 78 | # For each form that we have, define a class for accessing its 79 | # leads. So, if you have a form called "User Registration", we'll 80 | # define a class called UserRegistration, allowing you to do 81 | # things like: 82 | # 83 | # WPDB::GravityForms::UserRegistration.where(:date_registered => '2013-01-01').each do { |l| puts "#{l.first_name} #{l.last_name}" } 84 | # 85 | # All fields in the form are available as accessors. 86 | class ModelGenerator 87 | attr_reader :models 88 | 89 | def initialize(forms = nil) 90 | @forms = Array(forms) || Form.all 91 | @models = [] 92 | end 93 | 94 | def generate 95 | @forms.each do |form| 96 | form_name = WPDB.camelize(form.title) 97 | form_class = build_class(form) 98 | 99 | @models << WPDB::GravityForms.const_set(form_name, form_class) 100 | end 101 | end 102 | 103 | private 104 | 105 | # Constructs a new Ruby class, inherited from Sequel::Model, that 106 | # will hold our model-like functionality. Sequel gives us most of 107 | # this for free, by virtue of defining a dataset. 108 | def build_class(form) 109 | @labels = [] 110 | 111 | dataset = WPDB.db[:"#{WPDB.prefix}rg_lead___l"] 112 | .where(:"l__form_id" => form.id) 113 | 114 | dataset = join_fields(dataset, form.fields) 115 | dataset = dataset.select_all(:l) 116 | dataset = select_fields(dataset, form.fields) 117 | dataset = dataset.from_self 118 | 119 | Class.new(Sequel::Model) do 120 | set_dataset dataset 121 | end 122 | end 123 | 124 | def sanitise_label(original_label) 125 | @labels ||= [] 126 | 127 | original_label = original_label.to_s 128 | return "" unless original_label.length > 0 129 | 130 | i = 1 131 | 132 | label = WPDB.underscoreize(original_label) 133 | while @labels.include?(label) 134 | label = WPDB.underscoreize(original_label + i.to_s) 135 | i += 1 136 | end 137 | 138 | @labels << label 139 | 140 | label 141 | end 142 | 143 | def ignored_fields 144 | ["html_block"] 145 | end 146 | 147 | def join_fields(dataset, fields) 148 | fields.each_with_index do |field, i| 149 | next unless field && field['label'] 150 | next if ignored_fields.include?(field['label']) 151 | 152 | dataset = dataset.join_table( 153 | :left, 154 | :"#{WPDB.prefix}rg_lead_detail___ld#{i}", 155 | { :field_number => field['id'], :lead_id => :"l__id" } 156 | ) 157 | end 158 | 159 | dataset 160 | end 161 | 162 | def select_fields(dataset, fields) 163 | fields.each_with_index do |field, i| 164 | next unless field && field['label'] 165 | next if ignored_fields.include?(field['label']) 166 | 167 | field_name = sanitise_label(field['label']) 168 | 169 | next if field_name.empty? 170 | 171 | dataset = dataset.select_append(:"ld#{i}__value___#{field_name}") 172 | end 173 | 174 | dataset 175 | end 176 | end 177 | 178 | # When a request is made to, for example, 179 | # WPDB::GravityForms::SomeClass, this method will fire. If there's 180 | # a GravityForm whose title, when camelised, is "SomeClass", a model 181 | # will be created for that form. 182 | # 183 | # After the first time, the constant for that form will have been 184 | # created, and so this hook will no longer fire. 185 | def self.const_missing(name) 186 | Form.each do |form| 187 | if name.to_s == WPDB.camelize(form.title) 188 | ModelGenerator.new(form).generate 189 | return WPDB::GravityForms.const_get(name) 190 | end 191 | end 192 | 193 | raise "Form not found: #{name}" 194 | end 195 | end 196 | 197 | # Given a string, will convert it to a camel case suitable for use in 198 | # a Ruby constant (which means no non-alphanumeric characters and no 199 | # leading numbers). 200 | def self.camelize(string) 201 | string.gsub(/[^a-z0-9 ]/i, '') 202 | .gsub(/^[0-9]+/, '') 203 | .split(/\s+/) 204 | .map { |t| t.strip.capitalize } 205 | .join('') 206 | end 207 | 208 | # Given a string, will convert it an_underscored_value suitable for 209 | # use in a Ruby variable name/symbol. 210 | def self.underscoreize(string) 211 | string.downcase 212 | .gsub(/ +/, ' ') 213 | .gsub(' ', '_') 214 | .gsub(/[^a-z0-9_]/, '') 215 | end 216 | end 217 | -------------------------------------------------------------------------------- /lib/ruby-wpdb/link.rb: -------------------------------------------------------------------------------- 1 | module WPDB 2 | class Link < Sequel::Model(:"#{WPDB.prefix}links") 3 | include Termable 4 | 5 | one_to_many :termrelationships, 6 | key: :object_id, 7 | key_method: :obj_id, 8 | class: 'WPDB::TermRelationship' 9 | 10 | many_to_many :termtaxonomy, 11 | left_key: :object_id, 12 | right_key: :term_taxonomy_id, 13 | join_table: "#{WPDB.prefix}term_relationships", 14 | class: 'WPDB::TermTaxonomy' 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/ruby-wpdb/option.rb: -------------------------------------------------------------------------------- 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 25 | -------------------------------------------------------------------------------- /lib/ruby-wpdb/post.rb: -------------------------------------------------------------------------------- 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 'postmetas' 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 | one_to_many :children, 16 | key: :post_parent, 17 | class: self do |ds| 18 | ds.where(post_type: ['attachment', 'revision']).invert 19 | .where(post_parent: self.ID) 20 | end 21 | one_to_many :revisions, 22 | key: :post_parent, 23 | class: self, 24 | conditions: { post_type: 'revision' } 25 | 26 | one_to_many :attachments, 27 | key: :post_parent, 28 | class: self, 29 | conditions: { post_type: 'attachment' } 30 | 31 | # In order to use Sequel add_postmeta function. 32 | one_to_many :postmetas, class: 'WPDB::PostMeta' 33 | one_to_many :comments, key: :comment_post_ID, class: 'WPDB::Comment' 34 | one_to_many :termrelationships, 35 | key: :object_id, 36 | key_method: :obj_id, 37 | class: 'WPDB::TermRelationship' 38 | 39 | many_to_one :parent, class: self, key: :post_parent 40 | many_to_one :author, key: :post_author, class: 'WPDB::User' 41 | 42 | many_to_many :termtaxonomy, 43 | left_key: :object_id, 44 | right_key: :term_taxonomy_id, 45 | join_table: "#{WPDB.prefix}term_relationships", 46 | class: 'WPDB::TermTaxonomy' 47 | 48 | def validate 49 | super 50 | validates_presence [:post_title, :post_type, :post_status] 51 | validates_unique :post_name 52 | end 53 | 54 | def before_validation 55 | self.post_type ||= "post" 56 | self.post_status ||= "draft" 57 | self.post_parent ||= 0 58 | self.menu_order ||= 0 59 | self.comment_status ||= "open" 60 | self.ping_status ||= WPDB::Option.get_option("default_ping_status") 61 | self.post_date ||= Time.now 62 | self.post_date_gmt ||= Time.now.utc 63 | super 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /lib/ruby-wpdb/postmeta.rb: -------------------------------------------------------------------------------- 1 | module WPDB 2 | class PostMeta < Sequel::Model(:"#{WPDB.prefix}postmeta") 3 | many_to_one :post, class: 'WPDB::Post' 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /lib/ruby-wpdb/term.rb: -------------------------------------------------------------------------------- 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 | module Termable 15 | # For objects that have a relationship with termtaxonomies, this 16 | # module can be mixed in and gives the ability to add a term 17 | # directly to the model, rather than creating the relationship 18 | # yourself. Used by Post and Link. 19 | def add_term(term, taxonomy, description, count) 20 | if term.respond_to?(:term_id) 21 | term_id = term.term_id 22 | else 23 | term_id = term.to_i 24 | end 25 | 26 | term_taxonomy = WPDB::TermTaxonomy.where(term_id: term_id, taxonomy: taxonomy).first 27 | unless term_taxonomy 28 | term_taxonomy = WPDB::TermTaxonomy.create( 29 | term_id: term_id, 30 | taxonomy: taxonomy, 31 | description: description, 32 | count: count 33 | ) 34 | else 35 | term_taxonomy.count += count 36 | end 37 | 38 | add_termtaxonomy(term_taxonomy) 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/ruby-wpdb/term_relationship.rb: -------------------------------------------------------------------------------- 1 | module WPDB 2 | class TermRelationship < Sequel::Model(:"#{WPDB.prefix}term_relationships") 3 | def_column_alias(:obj_id, :object_id) 4 | 5 | many_to_one :post, class: 'WPDB::Post', key: :object_id 6 | many_to_one :link, class: 'WPDB::Link', key: :object_id 7 | 8 | many_to_one :termtaxonomy, 9 | class: 'WPDB::TermTaxonomy', 10 | key: :term_taxonomy_id 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/ruby-wpdb/term_taxonomy.rb: -------------------------------------------------------------------------------- 1 | module WPDB 2 | class TermTaxonomy < Sequel::Model(:"#{WPDB.prefix}term_taxonomy") 3 | one_to_many :termrelationships, class: 'WPDB::TermRelationship' 4 | 5 | many_to_one :term, class: 'WPDB::Term' 6 | 7 | many_to_many :posts, 8 | left_key: :term_taxonomy_id, 9 | right_key: :object_id, 10 | join_table: "#{WPDB.prefix}term_relationships", 11 | class: 'WPDB::Post' 12 | 13 | many_to_many :links, 14 | left_key: :term_taxonomy_id, 15 | right_key: :object_id, 16 | join_table: "#{WPDB.prefix}term_relationships", 17 | class: 'WPDB::Link' 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/ruby-wpdb/user.rb: -------------------------------------------------------------------------------- 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 | end 37 | -------------------------------------------------------------------------------- /lib/ruby-wpdb/usermeta.rb: -------------------------------------------------------------------------------- 1 | module WPDB 2 | class UserMeta < Sequel::Model(:"#{WPDB.user_prefix}usermeta") 3 | many_to_one :user, class: "WPDB::User" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /lib/ruby-wpdb/version.rb: -------------------------------------------------------------------------------- 1 | module WPDB 2 | VERSION = "1.2.3" 3 | end 4 | -------------------------------------------------------------------------------- /ruby-wpdb.gemspec: -------------------------------------------------------------------------------- 1 | $:.push File.expand_path("../lib", __FILE__) 2 | require "ruby-wpdb/version" 3 | 4 | Gem::Specification.new do |s| 5 | s.name = "ruby-wpdb" 6 | s.version = WPDB::VERSION 7 | s.date = Date.today.strftime("%Y-%m-%d") 8 | 9 | s.summary = "A Ruby ORM wrapper for WordPress" 10 | s.description = "ruby-wpdb gives you a painless way to access and interact with WordPress from Ruby, accessing posts, tags, and all other WordPress concepts as plain-old Ruby objects." 11 | 12 | s.authors = ["Rob Miller"] 13 | s.email = "rob@bigfish.co.uk" 14 | s.homepage = "http://github.com/robmiller/ruby-wpdb" 15 | 16 | s.license = "MIT" 17 | 18 | s.files = Dir.glob("{bin,lib,data,test}/**/*") + %w(LICENSE README.md Gemfile) 19 | s.require_path = 'lib' 20 | s.executables = ['ruby-wpdb'] 21 | 22 | s.add_runtime_dependency 'mysql2', '~> 0.3' 23 | s.add_runtime_dependency 'sequel', '~> 4.2' 24 | s.add_runtime_dependency 'sequel_sluggable', '~> 0.0.6' 25 | s.add_runtime_dependency 'php-serialize', '~> 1.1' 26 | s.add_runtime_dependency 'pry', '~> 0.9' 27 | s.add_runtime_dependency 'thor', '~> 0.18' 28 | 29 | s.add_development_dependency 'rake', '~> 10.1' 30 | s.add_development_dependency 'rubygems-tasks', '~> 0.2' 31 | 32 | s.add_development_dependency 'rspec', '~> 2.14' 33 | s.add_development_dependency 'rack-test', '~> 0.6' 34 | 35 | s.add_development_dependency "guard", "~> 2.6" 36 | s.add_development_dependency "guard-rspec", "~> 4.2" 37 | s.add_development_dependency "guard-bundler", "~> 2.0" 38 | 39 | s.add_development_dependency 'mutant', '~> 0.5' 40 | s.add_development_dependency 'mutant-rspec', '~> 0.5' 41 | end 42 | -------------------------------------------------------------------------------- /spec/lib/ruby-wpdb/comment_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../spec_helper' 2 | 3 | module WPDB 4 | describe Comment do 5 | before do 6 | @comment = Comment.create( 7 | comment_author: 'Testy Testerson', 8 | comment_author_email: 'testy@example.com', 9 | comment_content: 'Test' 10 | ) 11 | end 12 | 13 | it "creates a new comment" do 14 | @comment.comment_ID.should be > 0 15 | end 16 | 17 | it "attaches comments to posts" do 18 | post = Post.create(post_title: 'test', post_author: 1) 19 | post.ID.should be > 0 20 | 21 | post.add_comment(@comment) 22 | @comment.post.should be 23 | post.comments.should_not be_empty 24 | post.ID.should == post.comments.first.comment_post_ID 25 | 26 | post.destroy 27 | end 28 | 29 | it "adds commentmeta" do 30 | @comment.add_commentmeta(meta_key: 'test', meta_value: 'test') 31 | 32 | comment = Comment.where(comment_ID: @comment.comment_ID).first 33 | comment.commentmeta.should_not be_empty 34 | comment.commentmeta.first.meta_key.should == 'test' 35 | comment.commentmeta.first.meta_value.should == 'test' 36 | end 37 | 38 | after do 39 | @comment.destroy 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /spec/lib/ruby-wpdb/config_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../spec_helper' 2 | 3 | require "open-uri" 4 | 5 | module WPDB 6 | describe Config do 7 | let(:sample_yaml) { 8 | <<-EOYML 9 | username: username_here 10 | password: password_here 11 | hostname: localhost 12 | database: database_name_here 13 | prefix: wp_ 14 | EOYML 15 | } 16 | 17 | let(:sample_wordpress) { 18 | <<-EOWP 19 | define('DB_USER', "username_here"); 20 | define('DB_PASSWORD', 'password_here'); 21 | define('DB_HOST', 'localhost'); 22 | define('DB_PREFIX', "wp_"); 23 | define('DB_NAME', 'database_name_here'); 24 | EOWP 25 | } 26 | 27 | let(:different_prefix_yaml) { 28 | <<-EOYML 29 | username: username_here 30 | password: password_here 31 | hostname: localhost 32 | database: database_name_here 33 | prefix: prefix_ 34 | EOYML 35 | } 36 | 37 | let(:different_prefix_wordpress) { 38 | <<-EOWP 39 | define('DB_USER', "username_here"); 40 | define('DB_PASSWORD', 'password_here'); 41 | define('DB_HOST', 'localhost'); 42 | define('DB_PREFIX', "prefix_"); 43 | define('DB_NAME', 'database_name_here'); 44 | EOWP 45 | } 46 | 47 | let(:valid_uri) { "mysql2://username_here:password_here@localhost/database_name_here" } 48 | 49 | describe Config::WPConfig do 50 | it "correctly parses the sample wp-config" do 51 | sample_config = Pathname(__FILE__) + ".." + ".." + ".." + ".." + "data" + "wp-config-sample.php" 52 | live_url = "https://raw.githubusercontent.com/WordPress/WordPress/master/wp-config-sample.php" 53 | open(live_url) do |live| 54 | File.open(sample_config, "w") do |f| 55 | f.write live.read 56 | end 57 | end 58 | 59 | config = Config::WPConfig.new(sample_config).config 60 | 61 | config[:uri].should == valid_uri 62 | config[:prefix].should == "wp_" 63 | end 64 | 65 | it "parses wp-config files" do 66 | File.should_receive(:read).with("path/to/wp-config.php").and_return(sample_wordpress) 67 | 68 | config = Config::WPConfig.new("path/to/wp-config.php").config 69 | 70 | config[:uri].should == valid_uri 71 | config[:prefix].should == "wp_" 72 | end 73 | 74 | it "parses IO objects that return wp-config data" do 75 | io = StringIO.new(sample_wordpress) 76 | 77 | config = Config::WPConfig.new(io).config 78 | 79 | config[:uri].should == valid_uri 80 | config[:prefix].should == "wp_" 81 | end 82 | 83 | it "accepts different prefixes" do 84 | io = StringIO.new(different_prefix_wordpress) 85 | 86 | config = Config::WPConfig.new(io).config 87 | 88 | config[:prefix].should == "prefix_" 89 | end 90 | end 91 | 92 | describe Config::YAML do 93 | it "loads a local YAML file" do 94 | File.should_receive(:read).with("path/to/file.yaml").and_return(sample_yaml) 95 | 96 | config = Config::YAML.new("path/to/file.yaml").config 97 | 98 | config[:uri].should == valid_uri 99 | config[:prefix].should == "wp_" 100 | end 101 | 102 | it "parses IO objects that return YAML" do 103 | io = StringIO.new(sample_yaml) 104 | 105 | config = Config::YAML.new(io).config 106 | 107 | config[:uri].should == valid_uri 108 | config[:prefix].should == "wp_" 109 | end 110 | 111 | it "accepts different prefixes" do 112 | io = StringIO.new(different_prefix_yaml) 113 | 114 | config = Config::YAML.new(io).config 115 | 116 | config[:prefix].should == "prefix_" 117 | end 118 | end 119 | 120 | describe Config::AutoDiscover do 121 | let(:dir) { Pathname("path/to/dir") } 122 | 123 | describe "#file" do 124 | def test_config_file(file) 125 | File.stub(:exist?).and_call_original 126 | File.should_receive(:exist?).with(file).and_return(true) 127 | 128 | Config::AutoDiscover.new(dir).file.should == file 129 | end 130 | 131 | it "loads a wp-config.php file if there's one in the current directory" do 132 | test_config_file(dir + "wp-config.php") 133 | end 134 | 135 | it "loads a wp-config.php file if there's one in the directory above" do 136 | test_config_file(dir + ".." + "wp-config.php") 137 | end 138 | 139 | it "loads a wp-config.php file if there's one in a directory called wp/" do 140 | test_config_file(dir + "wp" + "wp-config.php") 141 | end 142 | 143 | it "loads a wp-config.php file if there's one in a directory called wordpress/" do 144 | test_config_file(dir + "wordpress" + "wp-config.php") 145 | end 146 | 147 | it "loads a config.yml file if there's one in the current directory" do 148 | test_config_file(dir + "config.yml") 149 | end 150 | 151 | it "should memoize the result" do 152 | file = dir + "wp-config.php" 153 | 154 | File.stub(:exist?).and_call_original 155 | File.should_receive(:exist?).exactly(1).times.with(file).and_return(true) 156 | 157 | config = Config::AutoDiscover.new(dir) 158 | config.file.should == file 159 | config.file.should == file 160 | end 161 | end 162 | end 163 | 164 | describe Config::AutoFormat do 165 | let(:dir) { Pathname("path/to/dir") } 166 | 167 | describe "#format" do 168 | it "recognises PHP files" do 169 | file = dir + "wp-config.php" 170 | 171 | config = Config::AutoFormat.new(file) 172 | config.format.should == Config::WPConfig 173 | end 174 | 175 | it "recognises YAML files" do 176 | file = dir + "test.yml" 177 | 178 | config = Config::AutoFormat.new(file) 179 | config.format.should == Config::YAML 180 | end 181 | end 182 | end 183 | end 184 | end 185 | -------------------------------------------------------------------------------- /spec/lib/ruby-wpdb/gravityforms_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../spec_helper' 2 | 3 | module WPDB 4 | describe GravityForms do 5 | let(:sample_fields) { 6 | [ 7 | { "id" => 1, "label" => "First name" }, 8 | { "id" => 2, "label" => "Last name" } 9 | ] 10 | } 11 | 12 | let(:sample_details) { 13 | [ 14 | GravityForms::Detail.new(lead_id: 1, form_id: 1, field_number: 1, value: "Rob"), 15 | GravityForms::Detail.new(lead_id: 1, form_id: 1, field_number: 2, value: "Miller") 16 | ] 17 | } 18 | 19 | let(:sample_leads) { 20 | [ 21 | GravityForms::Lead.new 22 | ] 23 | } 24 | 25 | before(:each) do 26 | @form = GravityForms::Form.first 27 | @class_name = WPDB.camelize(@form.title).to_sym 28 | 29 | sample_leads.each do |lead| 30 | lead.stub(:details).and_return(sample_details) 31 | lead.stub(:form).and_return(@form) 32 | lead.stub(:values).and_return({"First name" => "Rob", "Last name" => "Miller"}) 33 | end 34 | end 35 | 36 | it "fetches a form" do 37 | @form.id.should be > 0 38 | end 39 | 40 | it "fetches forms by title" do 41 | GravityForms::Form.from_title(@form.title).id.should == @form.id 42 | end 43 | 44 | it "associates leads with a form" do 45 | @form.leads.should_not be_empty 46 | end 47 | 48 | it "associates lead detail with a lead" do 49 | @form.leads.first.details.should_not be_empty 50 | end 51 | 52 | it "gets the name of a field" do 53 | @form.stub(:fields).and_return(sample_fields) 54 | @form.field_name(1).should == "First name" 55 | @form.field_name(2).should == "Last name" 56 | end 57 | 58 | it "fetches the details of a lead as a hash" do 59 | fields = @form.leads.first.values 60 | @form.leads.first.details.each do |detail| 61 | fields.values.should include(detail.value) 62 | end 63 | end 64 | 65 | it "generates a CSV" do 66 | @form.stub(:fields).and_return(sample_fields) 67 | @form.stub(:leads).and_return(sample_leads) 68 | 69 | io = StringIO.new 70 | @form.to_csv(io) 71 | 72 | io.string.should == "First name,Last name\nRob,Miller\n" 73 | end 74 | 75 | it "generates unique names for labels" do 76 | generator = GravityForms::ModelGenerator.new 77 | generator.send(:sanitise_label, "Hello world").should == "hello_world" 78 | generator.send(:sanitise_label, "Hello world").should == "hello_world1" 79 | end 80 | 81 | it "lazily loads models for forms" do 82 | klass = GravityForms.const_get(@class_name) 83 | klass.should be_a(Class) 84 | end 85 | 86 | it "dynamically loads forms if they aren't loaded when requested" do 87 | GravityForms.send(:remove_const, @class_name) 88 | klass = GravityForms.const_get(@class_name) 89 | klass.should be_a(Class) 90 | end 91 | 92 | it "raises an error for forms that don't exist" do 93 | expect { GravityForms::AFormThatDoesNotExist }.to raise_error(/Form not found/) 94 | end 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /spec/lib/ruby-wpdb/link_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../spec_helper' 2 | 3 | module WPDB 4 | describe Link do 5 | before do 6 | @link = Link.create(link_url: 'http://example.com', link_name: 'Example') 7 | end 8 | 9 | it "creates links" do 10 | @link.link_id.should be > 0 11 | end 12 | 13 | it "can attach terms to links" do 14 | term = Term.create(name: 'terming links') 15 | @link.add_term(term, 'category') 16 | @link.save 17 | 18 | @link.link_id.should be > 0 19 | @link.termtaxonomy.should_not be_empty 20 | @link.termtaxonomy.first.term_id.should == term.term_id 21 | @link.termtaxonomy.first.taxonomy.should == 'category' 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/lib/ruby-wpdb/option_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../spec_helper' 2 | 3 | module WPDB 4 | describe Option do 5 | before do 6 | @option = Option.create(option_name: 'test', option_value: 'test') 7 | end 8 | 9 | it "creates options" do 10 | @option.option_id.should be > 0 11 | end 12 | 13 | it "enforces the uniqueness of option names" do 14 | expect { 15 | Option.create(option_name: 'test', option_value: 'test') 16 | }.to raise_error Sequel::UniqueConstraintViolation 17 | end 18 | 19 | it "has a shorthand for fetching options" do 20 | Option.get_option('test').should == 'test' 21 | Option.get_option('non-existent-key').should_not be 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/lib/ruby-wpdb/post_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../spec_helper' 2 | 3 | module WPDB 4 | describe Post do 5 | before do 6 | @post = Post.create(post_title: 'Hello world', post_author: 1) 7 | end 8 | 9 | it "creates a new post" do 10 | @post.ID.should be > 0 11 | end 12 | 13 | it "fetches all posts" do 14 | Post.all.should_not be_empty 15 | end 16 | 17 | it "manages postmeta" do 18 | @post.add_postmeta(meta_key: 'test', meta_value: 'test') 19 | @post.save 20 | 21 | meta_value = @post.postmeta.first.meta_value 22 | meta_value.should == 'test' 23 | end 24 | 25 | it "manages the hierarchy of posts" do 26 | @post.add_child(Post.create(post_title: 'Child', post_author: 1)) 27 | @post.save 28 | 29 | @post.children.first.post_title.should == 'Child' 30 | end 31 | 32 | it "fetches revisions of posts" do 33 | revision = Post.create( 34 | post_type: 'revision', 35 | post_title: 'Revision', 36 | post_parent: @post.ID, 37 | post_author: 1 38 | ) 39 | 40 | @post.revisions.first.post_title.should == 'Revision' 41 | end 42 | 43 | it "fetches attachments to posts" do 44 | attachment = Post.create( 45 | post_type: 'attachment', 46 | post_title: 'Attachment', 47 | post_parent: @post.ID, 48 | post_author: 1 49 | ) 50 | 51 | @post.attachments.first.post_title.should == 'Attachment' 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /spec/lib/ruby-wpdb/readme_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../spec_helper' 2 | 3 | # Examples from the README; it's obviously important that they work 4 | # since they're people's first exposure to the project! 5 | module WPDB 6 | describe "README" do 7 | it "performs more complex queries" do 8 | post = Post.create(:post_title => 'aaaaa', :post_author => 1) 9 | post.add_postmeta(:meta_key => 'image', :meta_value => 'test') 10 | post.save 11 | 12 | meta_value = Post.first(:post_title => /^[a-z]+$/, :ID => post.ID) 13 | .postmeta_dataset.first(:meta_key => 'image').meta_value 14 | meta_value.should == 'test' 15 | end 16 | 17 | it "creates records" do 18 | post = Post.create(:post_title => 'Test', :post_content => 'Testing, testing, 123', :post_author => 1) 19 | post.ID.should be > 0 20 | end 21 | 22 | it "creates posts, users, and tags all in one go" do 23 | author = User.create( 24 | :user_login => 'fred', 25 | :user_email => 'fred@example.com' 26 | ) 27 | 28 | term = Term.create(:name => 'Fred Stuff') 29 | 30 | post = Post.create( 31 | :post_title => 'Hello from Fred', 32 | :post_content => 'Hello, world', 33 | :author => author 34 | ) 35 | post.add_term(term, 'tag') 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/lib/ruby-wpdb/term_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../spec_helper' 2 | 3 | module WPDB 4 | describe Term do 5 | before do 6 | @term = Term.create(name: 'test') 7 | @term_taxonomy = TermTaxonomy.create(term_id: @term.term_id, taxonomy: 'category') 8 | end 9 | 10 | it "saves terms" do 11 | @term.term_id.should be > 0 12 | end 13 | 14 | it "attaches terms to posts" do 15 | post = Post.create(post_title: 'test', post_author: 1) 16 | post.add_termtaxonomy(@term_taxonomy) 17 | post.save 18 | 19 | post.ID.should be > 0 20 | post.termtaxonomy.length.should be > 0 21 | post.termtaxonomy.first.term_id.should == @term.term_id 22 | post.termtaxonomy.first.taxonomy.should == 'category' 23 | end 24 | 25 | it "attaches terms to posts with the shorthand" do 26 | post = Post.create(post_title: 'test', post_author: 1) 27 | post.add_term(@term, 'category') 28 | post.save 29 | 30 | post.ID.should be > 0 31 | post.termtaxonomy.length.should be > 0 32 | post.termtaxonomy.first.term_id.should == @term.term_id 33 | post.termtaxonomy.first.taxonomy.should == 'category' 34 | end 35 | 36 | it "attaches terms to posts by ID" do 37 | post = Post.create(post_title: 'test', post_author: 1) 38 | post.add_term(@term.term_id, 'category') 39 | post.save 40 | 41 | post.ID.should be > 0 42 | post.termtaxonomy.length.should be > 0 43 | post.termtaxonomy.first.term_id.should == @term.term_id 44 | post.termtaxonomy.first.taxonomy.should == 'category' 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/lib/ruby-wpdb/user_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../spec_helper' 2 | 3 | module WPDB 4 | describe User do 5 | before do 6 | @user = User.create( 7 | user_login: 'test', 8 | user_pass: 'test', 9 | user_nicename: 'Testy Testerson', 10 | user_email: 'test@example.com', 11 | user_registered: DateTime.now 12 | ) 13 | end 14 | 15 | it "creates a new user" do 16 | @user.ID.should be > 0 17 | end 18 | 19 | it "adds usermeta" do 20 | @user.add_usermeta(meta_key: 'test', meta_value: 'test') 21 | @user.save 22 | 23 | user = User.where(ID: @user.ID).first 24 | meta_value = user.usermeta.first.meta_value 25 | meta_value.should == 'test' 26 | end 27 | 28 | it "hashes passwords" do 29 | @user.save 30 | @user.user_pass.should == Digest::MD5.hexdigest('test') 31 | end 32 | 33 | it "registers the authorship of posts" do 34 | post = Post.create(post_title: "Testy's first post") 35 | @user.add_post(post) 36 | @user.reload 37 | 38 | @user.posts.first.post_title.should == "Testy's first post" 39 | post.post_author.should == @user.ID 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /spec/lib/ruby-wpdb_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative "../spec_helper" 2 | 3 | describe WPDB do 4 | describe ".underscoreize" do 5 | it "converts spaces to underscores" do 6 | WPDB.underscoreize("foo bar").should == "foo_bar" 7 | end 8 | 9 | it "copes with multiple spaces" do 10 | WPDB.underscoreize("foo bar").should == "foo_bar" 11 | end 12 | 13 | it "removes special characters" do 14 | WPDB.underscoreize("foo!@£$%^&*()-+[]{};:'\"\| bar").should == "foo_bar" 15 | end 16 | 17 | it "retains letters, numbers, and underscores" do 18 | WPDB.underscoreize("foo123_456bar").should == "foo123_456bar" 19 | end 20 | 21 | it "forces lower-case" do 22 | WPDB.underscoreize("FOO").should == "foo" 23 | end 24 | end 25 | 26 | describe ".camelize" do 27 | it "strips anything but letters, numbers, and spaces" do 28 | WPDB.camelize("foo123!@£$%^&*()-+[]{};:'\"\| bar").should == "Foo123Bar" 29 | end 30 | 31 | it "strips leading numbers but retains others" do 32 | WPDB.camelize("1 foo 1 bar").should == "Foo1Bar" 33 | end 34 | 35 | it "collapses multiple spaces" do 36 | WPDB.camelize("foo bar").should == "FooBar" 37 | end 38 | 39 | it "copes with leading spaces" do 40 | WPDB.camelize(" foo bar").should == "FooBar" 41 | end 42 | 43 | it "copes with trailing spaces" do 44 | WPDB.camelize("foo bar ").should == "FooBar" 45 | end 46 | 47 | it "converts all-uppercase to camelcase" do 48 | WPDB.camelize("FOO BAR").should == "FooBar" 49 | end 50 | end 51 | 52 | describe "#config_file" do 53 | it "accepts a wp-config.php file" do 54 | WPDB.config_file("path/to/wp-config.php").format.should == WPDB::Config::WPConfig 55 | end 56 | 57 | it "accepts a YAML file" do 58 | WPDB.config_file("path/to/config.yml").format.should == WPDB::Config::YAML 59 | WPDB.config_file("path/to/config.yaml").format.should == WPDB::Config::YAML 60 | end 61 | 62 | it "raises an error when given an unknown file" do 63 | expect { WPDB.config_file("path/to/config.jpg") }.to raise_error(WPDB::ConfigFileError) 64 | end 65 | 66 | it "looks for files if none is given" do 67 | File.stub(:exist? => true) 68 | WPDB::Config::AutoDiscover.should_receive(:new).and_call_original 69 | WPDB.config_file(nil) 70 | end 71 | 72 | it "raises an error when given no file and no config files exist" do 73 | File.stub(:exist? => false) 74 | expect { WPDB.config_file(nil) }.to raise_error(WPDB::ConfigFileError) 75 | end 76 | end 77 | end 78 | 79 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $:.unshift File.expand_path('../lib', File.dirname(__FILE__)) 2 | require 'bundler/setup' 3 | 4 | require 'yaml' 5 | 6 | require 'sequel' 7 | 8 | require 'ruby-wpdb' 9 | 10 | WPDB.from_config(Pathname(__FILE__) + ".." + ".." + "config.yml") 11 | 12 | RSpec.configure do |c| 13 | c.around(:each) do |example| 14 | WPDB.db.transaction(rollback: :always){ example.run } 15 | end 16 | end 17 | 18 | require 'logger' 19 | WPDB.db.logger = Logger.new('data/query.log') 20 | 21 | --------------------------------------------------------------------------------