diary-ruby 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.travis.yml +2 -0
- data/README.md +1 -1
- data/diary-ruby.gemspec +1 -0
- data/exe/diaryrb +57 -50
- data/lib/diary-ruby.rb +6 -2
- data/lib/diary-ruby/database.rb +22 -0
- data/lib/diary-ruby/database/migrator.rb +90 -0
- data/lib/diary-ruby/database/query.rb +269 -0
- data/lib/diary-ruby/ext/concern.rb +166 -0
- data/lib/diary-ruby/model.rb +98 -0
- data/lib/diary-ruby/models/entry.rb +151 -0
- data/lib/diary-ruby/parser.rb +1 -2
- data/lib/diary-ruby/server/server.rb +8 -28
- data/lib/diary-ruby/server/views/index.erb +1 -1
- data/lib/diary-ruby/version.rb +1 -1
- metadata +22 -3
- data/lib/diary-ruby/entry.rb +0 -85
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 2f7c7aa9aab6f4119776f1ec8e197487beddc6b0
         | 
| 4 | 
            +
              data.tar.gz: d706e3f057f2ba5911e79bf8273057127b21db19
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 1b43c04f2ae1cd94d0e2df2ea58c351a3969b65cf95778f1018bf1d90d586f337cee7cbb8b2be4dd9e39cb435956f10f8b334e79c27881dbc595c9929b9ea88d
         | 
| 7 | 
            +
              data.tar.gz: 82806e6364fb1405b7c521769a1cfac4b2cab2a8da4cc495e42f0e7cc4e1f0ad10bb69d6fc7d7702cf042eb5a29aa4881dd4d5620c89817b3d745117560785aa
         | 
    
        data/.gitignore
    CHANGED
    
    
    
        data/.travis.yml
    CHANGED
    
    
    
        data/README.md
    CHANGED
    
    
    
        data/diary-ruby.gemspec
    CHANGED
    
    
    
        data/exe/diaryrb
    CHANGED
    
    | @@ -7,21 +7,23 @@ require 'thread' | |
| 7 7 |  | 
| 8 8 | 
             
            DEFAULT_DIARY = "diaryrb.store"
         | 
| 9 9 |  | 
| 10 | 
            -
            prompt_for_password = false
         | 
| 10 | 
            +
            # prompt_for_password = false
         | 
| 11 11 | 
             
            opts = Slop.parse do |o|
         | 
| 12 | 
            -
              # o.string '-c', '--configuration', 'config file location'
         | 
| 13 12 | 
             
              o.string '-d', '--diary', "choose diary storage file (leave blank for default, #{DEFAULT_DIARY})"
         | 
| 14 | 
            -
              o.string '- | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
             | 
| 18 | 
            -
              end
         | 
| 13 | 
            +
              # o.string '-c', '--configuration', 'config file location'
         | 
| 14 | 
            +
              # o.string '-p', '--passphrase', 'Use given encryption passphrase or prompt if option is used but no passphrase is given.', default: false do |v|
         | 
| 15 | 
            +
              #   if v.start_with?('-') || v.nil? || v.strip.size == 0
         | 
| 16 | 
            +
              #     prompt_for_password = true
         | 
| 17 | 
            +
              #   end
         | 
| 18 | 
            +
              # end
         | 
| 19 19 |  | 
| 20 20 | 
             
              # usage modes
         | 
| 21 21 | 
             
              o.separator ''
         | 
| 22 22 | 
             
              o.separator "Actions (can't be used in combination):"
         | 
| 23 | 
            +
              o.bool '-x', '--export', 'export all entries immediately to JSON'
         | 
| 23 24 | 
             
              o.string '-e', '--edit', 'edit a specific post'
         | 
| 24 25 | 
             
              o.bool '-l', '--list', 'list all posts by date'
         | 
| 26 | 
            +
              o.string '-t', '--tag', 'list entries, filtered by tag'
         | 
| 25 27 | 
             
              o.bool '-s', '--serve', 'start Diary webserver'
         | 
| 26 28 |  | 
| 27 29 | 
             
              o.separator ''
         | 
| @@ -57,50 +59,50 @@ end | |
| 57 59 |  | 
| 58 60 | 
             
            Diary::Configuration.current_diary = _diary
         | 
| 59 61 |  | 
| 60 | 
            -
            _passphrase = nil
         | 
| 61 | 
            -
            if prompt_for_password
         | 
| 62 | 
            -
              require 'io/console'
         | 
| 63 | 
            -
              print "Enter passphrase (leave blank for none): "
         | 
| 64 | 
            -
              _passphrase = STDIN.noecho {|io| io.gets}.chomp
         | 
| 65 | 
            -
            elsif opts[:passphrase] && opts[:passphrase].strip.size > 0
         | 
| 66 | 
            -
              _passphrase = opts[:passphrase]
         | 
| 67 | 
            -
            elsif ENV['PASSPHRASE']
         | 
| 68 | 
            -
              _passphrase = ENV['PASSPHRASE']
         | 
| 69 | 
            -
            elsif Diary::Configuration.passphrase
         | 
| 70 | 
            -
              _passphrase = Diary::Configuration.passphrase
         | 
| 71 | 
            -
            end
         | 
| 72 | 
            -
             | 
| 73 62 | 
             
            diary_path = Diary::Configuration.path || Diary::Configuration.current_diary
         | 
| 74 | 
            -
             | 
| 75 | 
            -
            if _passphrase.nil? || _passphrase.size == 0
         | 
| 76 | 
            -
              Diary.debug "LOADING WITH NO PASSPHRASE!"
         | 
| 77 | 
            -
              $store = Diary::Store.new(diary_path)
         | 
| 78 | 
            -
            else
         | 
| 79 | 
            -
              Diary.debug "LOADING WITH PASSPHRASE #{ _passphrase.gsub(/./, '*') }"
         | 
| 80 | 
            -
              $store = Diary::SecureStore.new(diary_path, _passphrase)
         | 
| 81 | 
            -
            end
         | 
| 63 | 
            +
            database = Diary::Database.new(diary_path)
         | 
| 82 64 |  | 
| 83 | 
            -
            #  | 
| 84 | 
            -
             | 
| 85 | 
            -
             | 
| 86 | 
            -
              db[:entries] = db[:entries].compact.uniq.sort
         | 
| 87 | 
            -
              db[:tags] ||= []
         | 
| 88 | 
            -
            end
         | 
| 65 | 
            +
            # make sure we're always up to date
         | 
| 66 | 
            +
            migrator = Diary::Migrator.new(database)
         | 
| 67 | 
            +
            migrator.migrate!
         | 
| 89 68 |  | 
| 90 | 
            -
             | 
| 91 | 
            -
             | 
| 69 | 
            +
            # initialize ORM
         | 
| 70 | 
            +
            Diary::Model.connection = database
         | 
| 71 | 
            +
             | 
| 72 | 
            +
            if opts.export?
         | 
| 92 73 | 
             
              puts ''
         | 
| 93 74 |  | 
| 94 | 
            -
              if  | 
| 75 | 
            +
              if Diary::Entry.count == 0
         | 
| 95 76 | 
             
                puts "No entries"
         | 
| 96 77 | 
             
                exit
         | 
| 97 78 | 
             
              else
         | 
| 98 | 
            -
                 | 
| 99 | 
            -
             | 
| 79 | 
            +
                require 'json'
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                output = []
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                Diary::Entry.order('created_at DESC').each do |entry|
         | 
| 84 | 
            +
                  output << entry.to_hash
         | 
| 85 | 
            +
                end
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                puts JSON.pretty_generate(output)
         | 
| 88 | 
            +
              end
         | 
| 89 | 
            +
            elsif opts.list?
         | 
| 90 | 
            +
              puts ''
         | 
| 91 | 
            +
              if Diary::Entry.count == 0
         | 
| 92 | 
            +
                puts "No entries"
         | 
| 93 | 
            +
                exit
         | 
| 94 | 
            +
              else
         | 
| 95 | 
            +
                Diary::Entry.order('created_at DESC').each do |entry|
         | 
| 96 | 
            +
                  puts entry.summary
         | 
| 100 97 | 
             
                end
         | 
| 101 98 | 
             
              end
         | 
| 99 | 
            +
            elsif opts[:tag] && (tag_id = Diary::Model.select_value('select rowid from tags where name = ?', opts[:tag]))
         | 
| 100 | 
            +
              entry_ids = Diary::Model.select_values('select entry_id from taggings where tag_id = ?', tag_id)
         | 
| 101 | 
            +
              Diary::Entry.where(date_key: entry_ids).each do |entry|
         | 
| 102 | 
            +
                puts entry.summary
         | 
| 103 | 
            +
              end
         | 
| 102 104 | 
             
            elsif opts.serve?
         | 
| 103 | 
            -
              Diary::Server.store = $store
         | 
| 105 | 
            +
              # Diary::Server.store = $store
         | 
| 104 106 | 
             
              t = Thread.new do
         | 
| 105 107 | 
             
                Diary::Server.run!
         | 
| 106 108 | 
             
              end
         | 
| @@ -114,7 +116,7 @@ else | |
| 114 116 |  | 
| 115 117 | 
             
              def parse_and_store(file)
         | 
| 116 118 | 
             
                diary_entry = Diary::Parser.parse_file(file)
         | 
| 117 | 
            -
                 | 
| 119 | 
            +
                diary_entry.save!
         | 
| 118 120 | 
             
              end
         | 
| 119 121 |  | 
| 120 122 | 
             
              # create a tempfile to store entry in progress in EDITOR
         | 
| @@ -127,18 +129,18 @@ else | |
| 127 129 | 
             
                time: Time.now.strftime("%T"),
         | 
| 128 130 | 
             
                tags: "",
         | 
| 129 131 | 
             
                title: "",
         | 
| 130 | 
            -
                 | 
| 132 | 
            +
                body: "text goes here"
         | 
| 131 133 | 
             
              }
         | 
| 132 134 |  | 
| 133 | 
            -
              # if --edit option is used with a valid entry, load it
         | 
| 134 | 
            -
              if opts[:edit] && ( | 
| 135 | 
            -
                entry = Diary::Entry. | 
| 136 | 
            -
                entry_source = entry.to_hash
         | 
| 137 | 
            -
                 | 
| 135 | 
            +
              # # if --edit option is used with a valid entry, load it
         | 
| 136 | 
            +
              if opts[:edit] && Diary::Entry.exists?(date_key: opts[:edit])
         | 
| 137 | 
            +
                entry = Diary::Entry.find(date_key: opts[:edit])
         | 
| 138 | 
            +
                entry_source = entry.to_hash if entry
         | 
| 139 | 
            +
                # FIXME: set tags
         | 
| 138 140 | 
             
              end
         | 
| 139 141 |  | 
| 140 142 | 
             
              # prepare entry and launch editor
         | 
| 141 | 
            -
              tmpl = Diary::Entry.generate(entry_source | 
| 143 | 
            +
              tmpl = Diary::Entry.generate(entry_source)
         | 
| 142 144 | 
             
              file.write(tmpl)
         | 
| 143 145 |  | 
| 144 146 | 
             
              ed = "vim -f"
         | 
| @@ -149,12 +151,17 @@ else | |
| 149 151 | 
             
              end
         | 
| 150 152 |  | 
| 151 153 | 
             
              pid = fork do
         | 
| 152 | 
            -
                 | 
| 154 | 
            +
                # split the editor into a separate process
         | 
| 155 | 
            +
                command = if /%s/ =~ ed
         | 
| 156 | 
            +
                            ed % [file.path]
         | 
| 157 | 
            +
                          else
         | 
| 158 | 
            +
                            "#{ ed } #{ file.path }"
         | 
| 159 | 
            +
                          end
         | 
| 160 | 
            +
                exec(command)
         | 
| 153 161 | 
             
              end
         | 
| 154 162 |  | 
| 155 163 | 
             
              # wait for child to finish, exit when the editor exits
         | 
| 156 164 | 
             
              exit_signal = Queue.new
         | 
| 157 | 
            -
             | 
| 158 165 | 
             
              trap("CLD") do
         | 
| 159 166 | 
             
                Diary.log "CHILD PID #{pid} TERMINATED"
         | 
| 160 167 | 
             
                exit_signal.push(true)
         | 
    
        data/lib/diary-ruby.rb
    CHANGED
    
    | @@ -1,6 +1,10 @@ | |
| 1 1 | 
             
            require "diary-ruby/version"
         | 
| 2 | 
            -
            require "diary-ruby/store"
         | 
| 3 | 
            -
            require "diary-ruby/ | 
| 2 | 
            +
            # require "diary-ruby/store"
         | 
| 3 | 
            +
            require "diary-ruby/database"
         | 
| 4 | 
            +
            require "diary-ruby/database/migrator"
         | 
| 5 | 
            +
            require "diary-ruby/database/query"
         | 
| 6 | 
            +
            require "diary-ruby/model"
         | 
| 7 | 
            +
            require "diary-ruby/models/entry"
         | 
| 4 8 | 
             
            require "diary-ruby/parser"
         | 
| 5 9 | 
             
            require "diary-ruby/configuration"
         | 
| 6 10 | 
             
            require "diary-ruby/server/server"
         | 
| @@ -0,0 +1,22 @@ | |
| 1 | 
            +
            require 'sqlite3'
         | 
| 2 | 
            +
            require 'diary-ruby/database/query'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module Diary
         | 
| 5 | 
            +
              class Database
         | 
| 6 | 
            +
                attr_reader :database
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                def initialize(path)
         | 
| 9 | 
            +
                  @database = SQLite3::Database.new(path)
         | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                def execute(*query)
         | 
| 13 | 
            +
                  if block_given?
         | 
| 14 | 
            +
                    @database.execute(*query) do |row|
         | 
| 15 | 
            +
                      yield row
         | 
| 16 | 
            +
                    end
         | 
| 17 | 
            +
                  else
         | 
| 18 | 
            +
                    @database.execute(*query)
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
            end
         | 
| @@ -0,0 +1,90 @@ | |
| 1 | 
            +
            MIGRATIONS = {}
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            INITIALIZE = %[
         | 
| 4 | 
            +
            CREATE TABLE IF NOT EXISTS `versions` (
         | 
| 5 | 
            +
              `version` TEXT NOT NULL,
         | 
| 6 | 
            +
              `migrated_at` TEXT DEFAULT NULL
         | 
| 7 | 
            +
            );
         | 
| 8 | 
            +
            ]
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            ## MIGRATION FORMAT:
         | 
| 11 | 
            +
            # MIGRATIONS[number] = array of sql statements
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            # 001 - create initial tables
         | 
| 14 | 
            +
            MIGRATIONS['001'] = [%[
         | 
| 15 | 
            +
              CREATE TABLE IF NOT EXISTS `entries` (
         | 
| 16 | 
            +
                `date_key` TEXT NOT NULL PRIMARY KEY,
         | 
| 17 | 
            +
                `day`  TEXT NOT NULL,
         | 
| 18 | 
            +
                `time` TEXT NOT NULL,
         | 
| 19 | 
            +
                `title` TEXT,
         | 
| 20 | 
            +
                `link` TEXT,
         | 
| 21 | 
            +
                `body` TEXT,
         | 
| 22 | 
            +
                `created_at` TEXT NOT NULL,
         | 
| 23 | 
            +
                `updated_at` TEXT DEFAULT NULL
         | 
| 24 | 
            +
              );
         | 
| 25 | 
            +
            ], %[
         | 
| 26 | 
            +
              CREATE INDEX IF NOT EXISTS `index_entries_on_key` on `entries` (`date_key`);
         | 
| 27 | 
            +
            ], %[
         | 
| 28 | 
            +
              CREATE TABLE IF NOT EXISTS `tags` (
         | 
| 29 | 
            +
                `name` TEXT DEFAULT NULL
         | 
| 30 | 
            +
              );
         | 
| 31 | 
            +
            ], %[
         | 
| 32 | 
            +
              CREATE TABLE IF NOT EXISTS `taggings` (
         | 
| 33 | 
            +
                `tag_id` INTEGER NOT NULL,
         | 
| 34 | 
            +
                `entry_id` TEXT NOT NULL
         | 
| 35 | 
            +
              );
         | 
| 36 | 
            +
            ], %[
         | 
| 37 | 
            +
              CREATE INDEX IF NOT EXISTS `index_taggings_on_tag_id` on `taggings` (`tag_id`);
         | 
| 38 | 
            +
            ], %[
         | 
| 39 | 
            +
              CREATE INDEX IF NOT EXISTS `index_taggings_on_entry_id` on `taggings` (`entry_id`);
         | 
| 40 | 
            +
            ]]
         | 
| 41 | 
            +
             | 
| 42 | 
            +
            MIGRATION_VERSIONS = MIGRATIONS.keys.sort
         | 
| 43 | 
            +
             | 
| 44 | 
            +
            module Diary
         | 
| 45 | 
            +
              class Migrator
         | 
| 46 | 
            +
                attr_reader :db
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                def initialize(db)
         | 
| 49 | 
            +
                  @db = db.database
         | 
| 50 | 
            +
                end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                def migrate!
         | 
| 53 | 
            +
                  exists = false
         | 
| 54 | 
            +
                  db.execute( "SELECT name FROM sqlite_master WHERE type='table' AND name='versions';" ) do |row|
         | 
| 55 | 
            +
                    if row
         | 
| 56 | 
            +
                      exists = true
         | 
| 57 | 
            +
                    end
         | 
| 58 | 
            +
                  end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                  if !exists
         | 
| 61 | 
            +
                    db.execute(INITIALIZE)
         | 
| 62 | 
            +
                  end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                  MIGRATION_VERSIONS.each do |version|
         | 
| 65 | 
            +
                    exists = false
         | 
| 66 | 
            +
                    on_date = nil
         | 
| 67 | 
            +
                    db.execute( "select rowid, migrated_at from versions WHERE version = '#{version}'" ) do |row|
         | 
| 68 | 
            +
                      if row
         | 
| 69 | 
            +
                        exists = true
         | 
| 70 | 
            +
                        on_date = row[1]
         | 
| 71 | 
            +
                      end
         | 
| 72 | 
            +
                    end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                    if !exists
         | 
| 75 | 
            +
                      Diary.debug("UPDATING DATABASE TO VERSION #{ version }")
         | 
| 76 | 
            +
                      if MIGRATIONS[version].is_a?(Array)
         | 
| 77 | 
            +
                        MIGRATIONS[version].each do |statement|
         | 
| 78 | 
            +
                          db.execute(statement)
         | 
| 79 | 
            +
                        end
         | 
| 80 | 
            +
                      else
         | 
| 81 | 
            +
                        db.execute(MIGRATIONS[version])
         | 
| 82 | 
            +
                      end
         | 
| 83 | 
            +
                      db.execute("INSERT INTO versions VALUES ('#{version}', strftime('%Y-%m-%dT%H:%M:%S+0000'));")
         | 
| 84 | 
            +
                    else
         | 
| 85 | 
            +
                      Diary.debug("AT #{ version } SINCE #{ on_date }")
         | 
| 86 | 
            +
                    end
         | 
| 87 | 
            +
                  end
         | 
| 88 | 
            +
                end
         | 
| 89 | 
            +
              end
         | 
| 90 | 
            +
            end
         | 
| @@ -0,0 +1,269 @@ | |
| 1 | 
            +
             | 
| 2 | 
            +
            module Diary
         | 
| 3 | 
            +
              module Query
         | 
| 4 | 
            +
                class Select
         | 
| 5 | 
            +
                  def initialize(table, context=nil)
         | 
| 6 | 
            +
                    @table_name = table
         | 
| 7 | 
            +
                    @context = context
         | 
| 8 | 
            +
                    @additions = []
         | 
| 9 | 
            +
                  end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  def context
         | 
| 12 | 
            +
                    @context
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  ## evaluation conditions, called when Select is given a context
         | 
| 16 | 
            +
                  # FIXME: it's gross that Query::Select knows about Model
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  def execute_in_context(sql)
         | 
| 19 | 
            +
                    Diary.debug("[Query::Select execute_in_context] connection.execute(#{ sql.inspect })")
         | 
| 20 | 
            +
                    if Array === sql
         | 
| 21 | 
            +
                      context.connection.execute(*sql)
         | 
| 22 | 
            +
                    else
         | 
| 23 | 
            +
                      context.connection.execute(sql)
         | 
| 24 | 
            +
                    end
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  def first
         | 
| 28 | 
            +
                    return self unless context
         | 
| 29 | 
            +
                    result = execute_in_context(self.limit(1).to_sql)
         | 
| 30 | 
            +
                    context.materialize(result)[0]
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  def all
         | 
| 34 | 
            +
                    return self unless context
         | 
| 35 | 
            +
                    result = execute_in_context(self.to_sql)
         | 
| 36 | 
            +
                    context.materialize(result)
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  def each(&block)
         | 
| 40 | 
            +
                    return self unless context
         | 
| 41 | 
            +
                    result = execute_in_context(self.to_sql)
         | 
| 42 | 
            +
                    context.materialize(result).each(&block)
         | 
| 43 | 
            +
                  end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                  def map(&block)
         | 
| 46 | 
            +
                    return self unless context
         | 
| 47 | 
            +
                    result = execute_in_context(self.to_sql)
         | 
| 48 | 
            +
                    context.materialize(result).map(&block)
         | 
| 49 | 
            +
                  end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                  def size
         | 
| 52 | 
            +
                    return self unless context
         | 
| 53 | 
            +
                    result = execute_in_context(self.to_sql)
         | 
| 54 | 
            +
                    result.size
         | 
| 55 | 
            +
                  end
         | 
| 56 | 
            +
                  alias :count :size
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                  ##
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                  def select(column_query)
         | 
| 61 | 
            +
                    @column_query = column_query
         | 
| 62 | 
            +
                  end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                  def exists?(*conditions)
         | 
| 65 | 
            +
                    if conditions.size > 0
         | 
| 66 | 
            +
                      @additions = []
         | 
| 67 | 
            +
                      @additions << Where.new(*conditions)
         | 
| 68 | 
            +
                      @additions << Limit.new(1)
         | 
| 69 | 
            +
                    end
         | 
| 70 | 
            +
                    c = self.count
         | 
| 71 | 
            +
                    c && c > 0
         | 
| 72 | 
            +
                  end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                  def where(*conditions)
         | 
| 75 | 
            +
                    # multiple wheres are OR'd
         | 
| 76 | 
            +
                    @additions << Where.new(*conditions)
         | 
| 77 | 
            +
                    self
         | 
| 78 | 
            +
                  end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                  def order(*conditions)
         | 
| 81 | 
            +
                    @additions << Order.new(*conditions)
         | 
| 82 | 
            +
                    self
         | 
| 83 | 
            +
                  end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                  def limit(*conditions)
         | 
| 86 | 
            +
                    @additions << Limit.new(*conditions)
         | 
| 87 | 
            +
                    self
         | 
| 88 | 
            +
                  end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                  def group_by(*conditions)
         | 
| 91 | 
            +
                    @additions << GroupBy.new(*conditions)
         | 
| 92 | 
            +
                    self
         | 
| 93 | 
            +
                  end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                  def to_sql
         | 
| 96 | 
            +
                    # combine @additions in order: WHERE () GROUP BY () ORDER () LIMIT ()
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                    sql_string = []
         | 
| 99 | 
            +
                    bind_vars = []
         | 
| 100 | 
            +
             | 
| 101 | 
            +
                    wheres = @additions.select {|a| Where === a}
         | 
| 102 | 
            +
                    group_bys = @additions.select {|a| GroupBy === a}
         | 
| 103 | 
            +
                    orders = @additions.select {|a| Order === a}
         | 
| 104 | 
            +
                    limits = @additions.select {|a| Limit === a}
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                    if wheres.size > 0
         | 
| 107 | 
            +
                      sql_string << "WHERE"
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                      where_params = []
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                      wheres = wheres.each do |w|
         | 
| 112 | 
            +
                        if w.has_bound_vars?
         | 
| 113 | 
            +
                          bind_vars << w.prepared_statement.bind_vars
         | 
| 114 | 
            +
                        end
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                        where_params << w.prepared_statement.sql_string
         | 
| 117 | 
            +
                      end
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                      sql_string << where_params.map {|wp|
         | 
| 120 | 
            +
                        "(#{ wp })"
         | 
| 121 | 
            +
                      }.join(' OR ')
         | 
| 122 | 
            +
                    end
         | 
| 123 | 
            +
             | 
| 124 | 
            +
                    if group_bys.size > 0
         | 
| 125 | 
            +
                      sql_string << "GROUP BY #{group_bys.map {|gb| gb.prepared_statement.sql_string}.join(', ')}"
         | 
| 126 | 
            +
                    end
         | 
| 127 | 
            +
             | 
| 128 | 
            +
                    if orders.size > 0
         | 
| 129 | 
            +
                      sql_string << "ORDER BY #{orders.map {|ord| ord.prepared_statement.sql_string}.join(', ')}"
         | 
| 130 | 
            +
                    end
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                    if limits.size > 0
         | 
| 133 | 
            +
                      # only 1 allowed, last takes precedence
         | 
| 134 | 
            +
                      limit = limits.last
         | 
| 135 | 
            +
                      sql_string << "LIMIT #{limit.prepared_statement.sql_string}"
         | 
| 136 | 
            +
                    end
         | 
| 137 | 
            +
             | 
| 138 | 
            +
                    query = [
         | 
| 139 | 
            +
                      "SELECT #{ @column_query || '*' }",
         | 
| 140 | 
            +
                      "FROM `#{ @table_name }`",
         | 
| 141 | 
            +
                      sql_string
         | 
| 142 | 
            +
                    ].join(' ')
         | 
| 143 | 
            +
             | 
| 144 | 
            +
                    # once to_sql is called, the Query is reset
         | 
| 145 | 
            +
                    @additions = []
         | 
| 146 | 
            +
             | 
| 147 | 
            +
                    # return sqlite compatible SQL
         | 
| 148 | 
            +
                    returning = if bind_vars.size > 0
         | 
| 149 | 
            +
                                  [query, bind_vars.flatten]
         | 
| 150 | 
            +
                                else
         | 
| 151 | 
            +
                                  query
         | 
| 152 | 
            +
                                end
         | 
| 153 | 
            +
                    returning
         | 
| 154 | 
            +
                  end
         | 
| 155 | 
            +
                end
         | 
| 156 | 
            +
             | 
| 157 | 
            +
                class SQLBoundParams
         | 
| 158 | 
            +
                  def initialize(left, right)
         | 
| 159 | 
            +
                    @for_sql_query = [left, right]
         | 
| 160 | 
            +
                  end
         | 
| 161 | 
            +
             | 
| 162 | 
            +
                  def sql_string
         | 
| 163 | 
            +
                    @for_sql_query[0]
         | 
| 164 | 
            +
                  end
         | 
| 165 | 
            +
             | 
| 166 | 
            +
                  def bind_vars
         | 
| 167 | 
            +
                    @for_sql_query[1]
         | 
| 168 | 
            +
                  end
         | 
| 169 | 
            +
                end
         | 
| 170 | 
            +
             | 
| 171 | 
            +
                class SQLString
         | 
| 172 | 
            +
                  def initialize(value)
         | 
| 173 | 
            +
                    @for_sql_query = value
         | 
| 174 | 
            +
                  end
         | 
| 175 | 
            +
             | 
| 176 | 
            +
                  def sql_string
         | 
| 177 | 
            +
                    @for_sql_query
         | 
| 178 | 
            +
                  end
         | 
| 179 | 
            +
                end
         | 
| 180 | 
            +
             | 
| 181 | 
            +
                class Node
         | 
| 182 | 
            +
                  def string_or_symbol?(value)
         | 
| 183 | 
            +
                    String === value || Symbol === value
         | 
| 184 | 
            +
                  end
         | 
| 185 | 
            +
             | 
| 186 | 
            +
                  def prepared_statement
         | 
| 187 | 
            +
                    @sql_result
         | 
| 188 | 
            +
                  end
         | 
| 189 | 
            +
             | 
| 190 | 
            +
                  def has_bound_vars?
         | 
| 191 | 
            +
                    SQLBoundParams === prepared_statement
         | 
| 192 | 
            +
                  end
         | 
| 193 | 
            +
                end
         | 
| 194 | 
            +
             | 
| 195 | 
            +
                class Where < Node
         | 
| 196 | 
            +
                  # convert conditions to AND'd list
         | 
| 197 | 
            +
                  # returns either string or (string, bind_params) 2-tuple
         | 
| 198 | 
            +
                  def initialize(*conditions)
         | 
| 199 | 
            +
                    @sql_result = if Hash === conditions[0]
         | 
| 200 | 
            +
                                    attrs = conditions[0]
         | 
| 201 | 
            +
             | 
| 202 | 
            +
                                    keys = attrs.keys
         | 
| 203 | 
            +
                                    vals = keys.map {|k| attrs[k]}
         | 
| 204 | 
            +
             | 
| 205 | 
            +
                                    and_string = keys.map do |k|
         | 
| 206 | 
            +
                                      if attrs[k].is_a?(Array)
         | 
| 207 | 
            +
                                        bind_hold = attrs[k].map {|_| '?'}.join(',')
         | 
| 208 | 
            +
                                        "`#{k}` in (#{bind_hold})"
         | 
| 209 | 
            +
                                      else
         | 
| 210 | 
            +
                                        "`#{k}` = ?"
         | 
| 211 | 
            +
                                      end
         | 
| 212 | 
            +
                                    end.join(' AND ')
         | 
| 213 | 
            +
             | 
| 214 | 
            +
                                    # (string, bind)
         | 
| 215 | 
            +
                                    SQLBoundParams.new(and_string, vals.flatten)
         | 
| 216 | 
            +
                                  elsif conditions.size > 1 && String === conditions[0]
         | 
| 217 | 
            +
                                    # assume (string, bind) given
         | 
| 218 | 
            +
                                    SQLBoundParams.new(conditions[0], conditions[1..-1])
         | 
| 219 | 
            +
                                  elsif conditions.size == 1 && String === conditions[0]
         | 
| 220 | 
            +
                                    SQLString.new(conditions[0])
         | 
| 221 | 
            +
                                  end
         | 
| 222 | 
            +
                  end
         | 
| 223 | 
            +
                end
         | 
| 224 | 
            +
             | 
| 225 | 
            +
                class Order < Node
         | 
| 226 | 
            +
                  def initialize(*conditions)
         | 
| 227 | 
            +
                    sql_string = if conditions.size == 1
         | 
| 228 | 
            +
                                   if string_or_symbol?(conditions[0])
         | 
| 229 | 
            +
                                     conditions[0]
         | 
| 230 | 
            +
                                   elsif Array === conditions[0]
         | 
| 231 | 
            +
                                     conditions.join(', ')
         | 
| 232 | 
            +
                                   else
         | 
| 233 | 
            +
                                     conditions[0].to_s
         | 
| 234 | 
            +
                                   end
         | 
| 235 | 
            +
                                 elsif conditions.size > 1
         | 
| 236 | 
            +
                                   conditions.join(', ')
         | 
| 237 | 
            +
                                 end
         | 
| 238 | 
            +
             | 
| 239 | 
            +
                    @sql_result = SQLString.new(sql_string)
         | 
| 240 | 
            +
                  end
         | 
| 241 | 
            +
                end
         | 
| 242 | 
            +
             | 
| 243 | 
            +
                class Limit < Node
         | 
| 244 | 
            +
                  def initialize(*conditions)
         | 
| 245 | 
            +
                    sql_string = if conditions.size == 1
         | 
| 246 | 
            +
                                   conditions[0]
         | 
| 247 | 
            +
                                 elsif conditions.size > 1
         | 
| 248 | 
            +
                                   conditions.join(', ')
         | 
| 249 | 
            +
                                 end
         | 
| 250 | 
            +
                    @sql_result = SQLString.new(sql_string)
         | 
| 251 | 
            +
                  end
         | 
| 252 | 
            +
                end
         | 
| 253 | 
            +
             | 
| 254 | 
            +
                class GroupBy < Node
         | 
| 255 | 
            +
                  def initialize(*conditions)
         | 
| 256 | 
            +
                    sql_string = if conditions.size == 1
         | 
| 257 | 
            +
                                   conditions[0]
         | 
| 258 | 
            +
                                 elsif conditions.size > 1
         | 
| 259 | 
            +
                                   conditions.join(', ')
         | 
| 260 | 
            +
                                 end
         | 
| 261 | 
            +
                    @sql_result = SQLString.new(sql_string)
         | 
| 262 | 
            +
                  end
         | 
| 263 | 
            +
                end
         | 
| 264 | 
            +
             | 
| 265 | 
            +
                # class Table
         | 
| 266 | 
            +
                #   extend Select
         | 
| 267 | 
            +
                # end
         | 
| 268 | 
            +
              end
         | 
| 269 | 
            +
            end
         | 
| @@ -0,0 +1,166 @@ | |
| 1 | 
            +
            # taken from: https://github.com/rails/rails/blob/master/activesupport/lib/active_support/concern.rb
         | 
| 2 | 
            +
            #
         | 
| 3 | 
            +
            # Copyright (c) 2005-2016 David Heinemeier Hansson
         | 
| 4 | 
            +
            #
         | 
| 5 | 
            +
            # Permission is hereby granted, free of charge, to any person obtaining
         | 
| 6 | 
            +
            # a copy of this software and associated documentation files (the
         | 
| 7 | 
            +
            # "Software"), to deal in the Software without restriction, including
         | 
| 8 | 
            +
            # without limitation the rights to use, copy, modify, merge, publish,
         | 
| 9 | 
            +
            # distribute, sublicense, and/or sell copies of the Software, and to
         | 
| 10 | 
            +
            # permit persons to whom the Software is furnished to do so, subject to
         | 
| 11 | 
            +
            # the following conditions:
         | 
| 12 | 
            +
            #
         | 
| 13 | 
            +
            # The above copyright notice and this permission notice shall be
         | 
| 14 | 
            +
            # included in all copies or substantial portions of the Software.
         | 
| 15 | 
            +
            #
         | 
| 16 | 
            +
            # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
         | 
| 17 | 
            +
            # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
         | 
| 18 | 
            +
            # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
         | 
| 19 | 
            +
            # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
         | 
| 20 | 
            +
            # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
         | 
| 21 | 
            +
            # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
         | 
| 22 | 
            +
            # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
         | 
| 23 | 
            +
            #
         | 
| 24 | 
            +
            module ActiveSupport
         | 
| 25 | 
            +
              # A typical module looks like this:
         | 
| 26 | 
            +
              #
         | 
| 27 | 
            +
              #   module M
         | 
| 28 | 
            +
              #     def self.included(base)
         | 
| 29 | 
            +
              #       base.extend ClassMethods
         | 
| 30 | 
            +
              #       base.class_eval do
         | 
| 31 | 
            +
              #         scope :disabled, -> { where(disabled: true) }
         | 
| 32 | 
            +
              #       end
         | 
| 33 | 
            +
              #     end
         | 
| 34 | 
            +
              #
         | 
| 35 | 
            +
              #     module ClassMethods
         | 
| 36 | 
            +
              #       ...
         | 
| 37 | 
            +
              #     end
         | 
| 38 | 
            +
              #   end
         | 
| 39 | 
            +
              #
         | 
| 40 | 
            +
              # By using <tt>ActiveSupport::Concern</tt> the above module could instead be
         | 
| 41 | 
            +
              # written as:
         | 
| 42 | 
            +
              #
         | 
| 43 | 
            +
              #   require 'active_support/concern'
         | 
| 44 | 
            +
              #
         | 
| 45 | 
            +
              #   module M
         | 
| 46 | 
            +
              #     extend ActiveSupport::Concern
         | 
| 47 | 
            +
              #
         | 
| 48 | 
            +
              #     included do
         | 
| 49 | 
            +
              #       scope :disabled, -> { where(disabled: true) }
         | 
| 50 | 
            +
              #     end
         | 
| 51 | 
            +
              #
         | 
| 52 | 
            +
              #     class_methods do
         | 
| 53 | 
            +
              #       ...
         | 
| 54 | 
            +
              #     end
         | 
| 55 | 
            +
              #   end
         | 
| 56 | 
            +
              #
         | 
| 57 | 
            +
              # Moreover, it gracefully handles module dependencies. Given a +Foo+ module
         | 
| 58 | 
            +
              # and a +Bar+ module which depends on the former, we would typically write the
         | 
| 59 | 
            +
              # following:
         | 
| 60 | 
            +
              #
         | 
| 61 | 
            +
              #   module Foo
         | 
| 62 | 
            +
              #     def self.included(base)
         | 
| 63 | 
            +
              #       base.class_eval do
         | 
| 64 | 
            +
              #         def self.method_injected_by_foo
         | 
| 65 | 
            +
              #           ...
         | 
| 66 | 
            +
              #         end
         | 
| 67 | 
            +
              #       end
         | 
| 68 | 
            +
              #     end
         | 
| 69 | 
            +
              #   end
         | 
| 70 | 
            +
              #
         | 
| 71 | 
            +
              #   module Bar
         | 
| 72 | 
            +
              #     def self.included(base)
         | 
| 73 | 
            +
              #       base.method_injected_by_foo
         | 
| 74 | 
            +
              #     end
         | 
| 75 | 
            +
              #   end
         | 
| 76 | 
            +
              #
         | 
| 77 | 
            +
              #   class Host
         | 
| 78 | 
            +
              #     include Foo # We need to include this dependency for Bar
         | 
| 79 | 
            +
              #     include Bar # Bar is the module that Host really needs
         | 
| 80 | 
            +
              #   end
         | 
| 81 | 
            +
              #
         | 
| 82 | 
            +
              # But why should +Host+ care about +Bar+'s dependencies, namely +Foo+? We
         | 
| 83 | 
            +
              # could try to hide these from +Host+ directly including +Foo+ in +Bar+:
         | 
| 84 | 
            +
              #
         | 
| 85 | 
            +
              #   module Bar
         | 
| 86 | 
            +
              #     include Foo
         | 
| 87 | 
            +
              #     def self.included(base)
         | 
| 88 | 
            +
              #       base.method_injected_by_foo
         | 
| 89 | 
            +
              #     end
         | 
| 90 | 
            +
              #   end
         | 
| 91 | 
            +
              #
         | 
| 92 | 
            +
              #   class Host
         | 
| 93 | 
            +
              #     include Bar
         | 
| 94 | 
            +
              #   end
         | 
| 95 | 
            +
              #
         | 
| 96 | 
            +
              # Unfortunately this won't work, since when +Foo+ is included, its <tt>base</tt>
         | 
| 97 | 
            +
              # is the +Bar+ module, not the +Host+ class. With <tt>ActiveSupport::Concern</tt>,
         | 
| 98 | 
            +
              # module dependencies are properly resolved:
         | 
| 99 | 
            +
              #
         | 
| 100 | 
            +
              #   require 'active_support/concern'
         | 
| 101 | 
            +
              #
         | 
| 102 | 
            +
              #   module Foo
         | 
| 103 | 
            +
              #     extend ActiveSupport::Concern
         | 
| 104 | 
            +
              #     included do
         | 
| 105 | 
            +
              #       def self.method_injected_by_foo
         | 
| 106 | 
            +
              #         ...
         | 
| 107 | 
            +
              #       end
         | 
| 108 | 
            +
              #     end
         | 
| 109 | 
            +
              #   end
         | 
| 110 | 
            +
              #
         | 
| 111 | 
            +
              #   module Bar
         | 
| 112 | 
            +
              #     extend ActiveSupport::Concern
         | 
| 113 | 
            +
              #     include Foo
         | 
| 114 | 
            +
              #
         | 
| 115 | 
            +
              #     included do
         | 
| 116 | 
            +
              #       self.method_injected_by_foo
         | 
| 117 | 
            +
              #     end
         | 
| 118 | 
            +
              #   end
         | 
| 119 | 
            +
              #
         | 
| 120 | 
            +
              #   class Host
         | 
| 121 | 
            +
              #     include Bar # It works, now Bar takes care of its dependencies
         | 
| 122 | 
            +
              #   end
         | 
| 123 | 
            +
              module Concern
         | 
| 124 | 
            +
                class MultipleIncludedBlocks < StandardError #:nodoc:
         | 
| 125 | 
            +
                  def initialize
         | 
| 126 | 
            +
                    super "Cannot define multiple 'included' blocks for a Concern"
         | 
| 127 | 
            +
                  end
         | 
| 128 | 
            +
                end
         | 
| 129 | 
            +
             | 
| 130 | 
            +
                def self.extended(base) #:nodoc:
         | 
| 131 | 
            +
                  base.instance_variable_set(:@_dependencies, [])
         | 
| 132 | 
            +
                end
         | 
| 133 | 
            +
             | 
| 134 | 
            +
                def append_features(base)
         | 
| 135 | 
            +
                  if base.instance_variable_defined?(:@_dependencies)
         | 
| 136 | 
            +
                    base.instance_variable_get(:@_dependencies) << self
         | 
| 137 | 
            +
                    return false
         | 
| 138 | 
            +
                  else
         | 
| 139 | 
            +
                    return false if base < self
         | 
| 140 | 
            +
                    @_dependencies.each { |dep| base.send(:include, dep) }
         | 
| 141 | 
            +
                    super
         | 
| 142 | 
            +
                    base.extend const_get(:ClassMethods) if const_defined?(:ClassMethods)
         | 
| 143 | 
            +
                    base.class_eval(&@_included_block) if instance_variable_defined?(:@_included_block)
         | 
| 144 | 
            +
                  end
         | 
| 145 | 
            +
                end
         | 
| 146 | 
            +
             | 
| 147 | 
            +
                def included(base = nil, &block)
         | 
| 148 | 
            +
                  if base.nil?
         | 
| 149 | 
            +
                    raise MultipleIncludedBlocks if instance_variable_defined?(:@_included_block)
         | 
| 150 | 
            +
             | 
| 151 | 
            +
                    @_included_block = block
         | 
| 152 | 
            +
                  else
         | 
| 153 | 
            +
                    super
         | 
| 154 | 
            +
                  end
         | 
| 155 | 
            +
                end
         | 
| 156 | 
            +
             | 
| 157 | 
            +
                def class_methods(&class_methods_module_definition)
         | 
| 158 | 
            +
                  mod = const_defined?(:ClassMethods, false) ?
         | 
| 159 | 
            +
                    const_get(:ClassMethods) :
         | 
| 160 | 
            +
                    const_set(:ClassMethods, Module.new)
         | 
| 161 | 
            +
             | 
| 162 | 
            +
                  mod.module_eval(&class_methods_module_definition)
         | 
| 163 | 
            +
                end
         | 
| 164 | 
            +
              end
         | 
| 165 | 
            +
            end
         | 
| 166 | 
            +
             | 
| @@ -0,0 +1,98 @@ | |
| 1 | 
            +
            require 'diary-ruby/ext/concern'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Diary
         | 
| 4 | 
            +
              module ModelQuery
         | 
| 5 | 
            +
                extend ActiveSupport::Concern
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                # Instance Methods
         | 
| 8 | 
            +
                def timestamp_sql
         | 
| 9 | 
            +
                  "strftime('%Y-%m-%dT%H:%M:%S+0000')"
         | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                module ClassMethods
         | 
| 13 | 
            +
                  def columns
         | 
| 14 | 
            +
                    @columns ||= connection.execute("PRAGMA table_info(#{table_name})")
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  def column_names
         | 
| 18 | 
            +
                    @column_names ||= columns.map {|col_info| col_info[1]}
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  def results_to_hashes(array_of_rows)
         | 
| 22 | 
            +
                    array_of_rows.map do |row|
         | 
| 23 | 
            +
                      Hash[ column_names.zip(row) ]
         | 
| 24 | 
            +
                    end
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  def materialize(array_of_rows)
         | 
| 28 | 
            +
                    results_to_hashes(array_of_rows).map do |record_hash|
         | 
| 29 | 
            +
                      if respond_to?(:from_hash)
         | 
| 30 | 
            +
                        from_hash(record_hash)
         | 
| 31 | 
            +
                      else
         | 
| 32 | 
            +
                        record_hash
         | 
| 33 | 
            +
                      end
         | 
| 34 | 
            +
                    end
         | 
| 35 | 
            +
                  end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  def find(attrs)
         | 
| 38 | 
            +
                    where(attrs).first
         | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  def new_select_relation
         | 
| 42 | 
            +
                    Diary::Query::Select.new(table_name, self)
         | 
| 43 | 
            +
                  end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                  %w(where order group_by limit exists?).each do |q_type|
         | 
| 46 | 
            +
                    define_method(q_type.to_sym) do |*conditions|
         | 
| 47 | 
            +
                      new_select_relation.send(q_type.to_sym, *conditions)
         | 
| 48 | 
            +
                    end
         | 
| 49 | 
            +
                  end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                  %w(all first count).each do |q_type|
         | 
| 52 | 
            +
                    define_method(q_type.to_sym) do
         | 
| 53 | 
            +
                      new_select_relation.send(q_type.to_sym)
         | 
| 54 | 
            +
                    end
         | 
| 55 | 
            +
                  end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                  %w(each map).each do |q_type|
         | 
| 58 | 
            +
                    define_method(q_type.to_sym) do |&block|
         | 
| 59 | 
            +
                      new_select_relation.send(q_type.to_sym, &block)
         | 
| 60 | 
            +
                    end
         | 
| 61 | 
            +
                  end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                end
         | 
| 64 | 
            +
              end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
              class Model
         | 
| 67 | 
            +
                include ModelQuery
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                class << self
         | 
| 70 | 
            +
                  def connection=(db)
         | 
| 71 | 
            +
                    @@connection = db
         | 
| 72 | 
            +
                  end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                  def connection
         | 
| 75 | 
            +
                    @@connection
         | 
| 76 | 
            +
                  end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                  # one-off queries
         | 
| 79 | 
            +
                  def execute(sql, *binds)
         | 
| 80 | 
            +
                    Diary.debug("[Model execute] #{ sql } #{ binds.inspect }")
         | 
| 81 | 
            +
                    connection.execute(sql, binds)
         | 
| 82 | 
            +
                  end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                  # one-off queries
         | 
| 85 | 
            +
                  def select_rows(sql, *binds)
         | 
| 86 | 
            +
                    execute(sql, *binds)
         | 
| 87 | 
            +
                  end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                  def select_values(sql, *binds)
         | 
| 90 | 
            +
                    select_rows(sql, *binds).map {|row| row[0]}
         | 
| 91 | 
            +
                  end
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                  def select_value(sql, *binds)
         | 
| 94 | 
            +
                    select_values(sql, *binds).first
         | 
| 95 | 
            +
                  end
         | 
| 96 | 
            +
                end
         | 
| 97 | 
            +
              end
         | 
| 98 | 
            +
            end
         | 
| @@ -0,0 +1,151 @@ | |
| 1 | 
            +
            TEMPLATE = "# last entry posted at %{last_update_at}
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            DAY   %{day}
         | 
| 4 | 
            +
            TIME  %{time}
         | 
| 5 | 
            +
            TAGS  %{tags}
         | 
| 6 | 
            +
            TITLE %{title}
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            ---
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            %{body}
         | 
| 11 | 
            +
            "
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            require 'rdiscount'
         | 
| 14 | 
            +
            require 'diary-ruby/model'
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            module Diary
         | 
| 17 | 
            +
              class Entry < Model
         | 
| 18 | 
            +
                attr_accessor :day, :time, :tags, :body, :link, :title, :date_key
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                def self.table_name
         | 
| 21 | 
            +
                  'entries'
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                def self.from_hash(record)
         | 
| 25 | 
            +
                  entry = self.new(
         | 
| 26 | 
            +
                    day: record['day'],
         | 
| 27 | 
            +
                    time: record['time'],
         | 
| 28 | 
            +
                    body: record['body'],
         | 
| 29 | 
            +
                    title: record['title'],
         | 
| 30 | 
            +
                    date_key: record['date_key'],
         | 
| 31 | 
            +
                  )
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  # taggings!
         | 
| 34 | 
            +
                  begin
         | 
| 35 | 
            +
                    tag_ids = select_values('select tag_id from taggings where entry_id = ?', [entry.identifier])
         | 
| 36 | 
            +
                    bind_hold = tag_ids.map {|_| '?'}.join(',')
         | 
| 37 | 
            +
                    entry.tags = select_values("select name from tags where rowid in (#{ bind_hold })", *tag_ids)
         | 
| 38 | 
            +
                  rescue => ex
         | 
| 39 | 
            +
                    Diary.debug "FAILED TO LOAD TAGS. #{ ex.message }"
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                  entry
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                def self.keygen(day, time)
         | 
| 46 | 
            +
                  "%s-%s" % [day, time]
         | 
| 47 | 
            +
                end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                def self.generate(options={})
         | 
| 50 | 
            +
                  options[:last_update_at] = connection.execute("select max(updated_at) from #{table_name}")[0] || ''
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                  # convert Arrays to dumb CSV
         | 
| 53 | 
            +
                  options.each do |(k, v)|
         | 
| 54 | 
            +
                    if v.is_a?(Array)
         | 
| 55 | 
            +
                      options[k] = v.join(', ')
         | 
| 56 | 
            +
                    end
         | 
| 57 | 
            +
                  end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                  TEMPLATE % options
         | 
| 60 | 
            +
                end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                def initialize(options={})
         | 
| 63 | 
            +
                  @day = options[:day]
         | 
| 64 | 
            +
                  @time = options[:time]
         | 
| 65 | 
            +
                  @tags = options[:tags] || []
         | 
| 66 | 
            +
                  @body = options[:body]
         | 
| 67 | 
            +
                  @title = options[:title]
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                  if options[:date_key].nil?
         | 
| 70 | 
            +
                    @date_key = identifier
         | 
| 71 | 
            +
                  else
         | 
| 72 | 
            +
                    @date_key = options[:date_key]
         | 
| 73 | 
            +
                  end
         | 
| 74 | 
            +
                end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                def identifier
         | 
| 77 | 
            +
                  self.class.keygen(day, time)
         | 
| 78 | 
            +
                end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                def formatted_body
         | 
| 81 | 
            +
                  RDiscount.new(body).to_html
         | 
| 82 | 
            +
                end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                def truncated_body
         | 
| 85 | 
            +
                  _truncated = body
         | 
| 86 | 
            +
                  if _truncated.size > 40
         | 
| 87 | 
            +
                    _truncated = "#{ _truncated[0..40] }..."
         | 
| 88 | 
            +
                  end
         | 
| 89 | 
            +
                  _truncated
         | 
| 90 | 
            +
                end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                def summary
         | 
| 93 | 
            +
                  "%-24s%-46s%s" % [date_key, truncated_body, tags.join(', ')]
         | 
| 94 | 
            +
                end
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                def to_hash
         | 
| 97 | 
            +
                  {
         | 
| 98 | 
            +
                    day: day,
         | 
| 99 | 
            +
                    time: time,
         | 
| 100 | 
            +
                    tags: tags,
         | 
| 101 | 
            +
                    body: body,
         | 
| 102 | 
            +
                    title: title,
         | 
| 103 | 
            +
                    date_key: date_key,
         | 
| 104 | 
            +
                  }
         | 
| 105 | 
            +
                end
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                def save!
         | 
| 108 | 
            +
                  if self.class.find(date_key: date_key)
         | 
| 109 | 
            +
                    # update record
         | 
| 110 | 
            +
                    sql = "UPDATE entries SET day=?, time=?, body=?, link=?, title=?, updated_at=#{timestamp_sql} WHERE date_key=?"
         | 
| 111 | 
            +
                    self.class.execute(sql, day, time, body, link, title, date_key)
         | 
| 112 | 
            +
                  else
         | 
| 113 | 
            +
                    # insert
         | 
| 114 | 
            +
                    sql = %[INSERT INTO entries (day, time, body, link, title, date_key, created_at, updated_at)
         | 
| 115 | 
            +
                            VALUES              (?,   ?,    ?,    ?,    ?,     ?,        #{timestamp_sql}, #{timestamp_sql})]
         | 
| 116 | 
            +
                    self.class.execute(sql, day, time, body, link, title, date_key)
         | 
| 117 | 
            +
                  end
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                  begin
         | 
| 120 | 
            +
                    update_tags!
         | 
| 121 | 
            +
                  rescue => ex
         | 
| 122 | 
            +
                    Diary.debug "FAILED TO UPDATE TAGS #{ tags.inspect }. #{ ex.message }"
         | 
| 123 | 
            +
                  end
         | 
| 124 | 
            +
                end
         | 
| 125 | 
            +
             | 
| 126 | 
            +
                def update_tags!
         | 
| 127 | 
            +
                  # clean out existing
         | 
| 128 | 
            +
                  Diary.debug "CLEANING `taggings`"
         | 
| 129 | 
            +
                  self.class.execute('delete from taggings where entry_id = ?', [identifier])
         | 
| 130 | 
            +
             | 
| 131 | 
            +
                  # add back
         | 
| 132 | 
            +
                  tags.each do |tag|
         | 
| 133 | 
            +
                    # is tag in db?
         | 
| 134 | 
            +
                    tag_id = self.class.select_value('select rowid from tags where name = ?', tag)
         | 
| 135 | 
            +
             | 
| 136 | 
            +
                    if tag_id.nil?
         | 
| 137 | 
            +
                      Diary.debug "CREATING TAG #{ tag.inspect }"
         | 
| 138 | 
            +
             | 
| 139 | 
            +
                      # exists
         | 
| 140 | 
            +
                      Diary.debug self.class.select_rows('PRAGMA table_info(tags)').inspect
         | 
| 141 | 
            +
                      self.class.execute("insert into tags (name) values (?)", [tag])
         | 
| 142 | 
            +
                      tag_id = self.class.select_value('select last_insert_rowid()')
         | 
| 143 | 
            +
                    end
         | 
| 144 | 
            +
             | 
| 145 | 
            +
                    Diary.debug "CREATING tagging"
         | 
| 146 | 
            +
                    self.class.execute('insert into taggings (tag_id, entry_id) values (?, ?)', [tag_id, identifier])
         | 
| 147 | 
            +
                  end
         | 
| 148 | 
            +
                end
         | 
| 149 | 
            +
              end
         | 
| 150 | 
            +
            end
         | 
| 151 | 
            +
             | 
    
        data/lib/diary-ruby/parser.rb
    CHANGED
    
    | @@ -46,11 +46,10 @@ module Diary | |
| 46 46 | 
             
                  Diary.debug "BODY #{ body.join(" ") }"
         | 
| 47 47 |  | 
| 48 48 | 
             
                  return Entry.new(
         | 
| 49 | 
            -
                    nil,
         | 
| 50 49 | 
             
                    day: metadata['day'],
         | 
| 51 50 | 
             
                    time: metadata['time'],
         | 
| 52 51 | 
             
                    tags: metadata['tags'],
         | 
| 53 | 
            -
                     | 
| 52 | 
            +
                    body: body.join("\n").strip,
         | 
| 54 53 | 
             
                    title: metadata['title'],
         | 
| 55 54 | 
             
                    key: key,
         | 
| 56 55 | 
             
                  )
         | 
| @@ -22,27 +22,10 @@ module Diary | |
| 22 22 | 
             
                end
         | 
| 23 23 |  | 
| 24 24 | 
             
                get '/' do
         | 
| 25 | 
            -
                   | 
| 26 | 
            -
             | 
| 27 | 
            -
                  if keys.nil?
         | 
| 28 | 
            -
                    store.write do |db|
         | 
| 29 | 
            -
                      db[:entries] = []
         | 
| 30 | 
            -
                    end
         | 
| 31 | 
            -
             | 
| 32 | 
            -
                    keys = []
         | 
| 25 | 
            +
                  @entries = Entry.order('created_at DESC').map do |entry_hash|
         | 
| 26 | 
            +
                    Entry.from_hash(entry_hash)
         | 
| 33 27 | 
             
                  end
         | 
| 34 28 |  | 
| 35 | 
            -
                  @entries = keys.uniq.map {|entry_key|
         | 
| 36 | 
            -
                    entry = store.read(entry_key)
         | 
| 37 | 
            -
             | 
| 38 | 
            -
                    if entry
         | 
| 39 | 
            -
                      logger.debug "LOAD #{ entry }"
         | 
| 40 | 
            -
                      Entry.from_store(entry)
         | 
| 41 | 
            -
                    else
         | 
| 42 | 
            -
                      nil
         | 
| 43 | 
            -
                    end
         | 
| 44 | 
            -
                  }.compact
         | 
| 45 | 
            -
             | 
| 46 29 | 
             
                  logger.info "returning keys: #{ keys }"
         | 
| 47 30 | 
             
                  logger.info "returning entries: #{ @entries }"
         | 
| 48 31 |  | 
| @@ -53,16 +36,13 @@ module Diary | |
| 53 36 | 
             
                  content_type :json
         | 
| 54 37 |  | 
| 55 38 | 
             
                  key = params[:key]
         | 
| 56 | 
            -
                  entry_hash =  | 
| 57 | 
            -
                  entry = Entry.from_store(entry_hash)
         | 
| 58 | 
            -
             | 
| 59 | 
            -
                  content = RDiscount.new entry.text
         | 
| 60 | 
            -
                  entry_hash[:formatted] = content.to_html
         | 
| 39 | 
            +
                  entry_hash = Entry.find(date_key: key)
         | 
| 61 40 |  | 
| 62 | 
            -
                  if  | 
| 41 | 
            +
                  if entry_hash
         | 
| 42 | 
            +
                    entry = Entry.from_store(entry_hash)
         | 
| 43 | 
            +
                    content = RDiscount.new entry['body']
         | 
| 44 | 
            +
                    entry_hash[:formatted] = content.to_html
         | 
| 63 45 | 
             
                    entry_hash.to_json
         | 
| 64 | 
            -
                  else
         | 
| 65 | 
            -
                    {}
         | 
| 66 46 | 
             
                  end
         | 
| 67 47 | 
             
                end
         | 
| 68 48 |  | 
| @@ -81,7 +61,7 @@ module Diary | |
| 81 61 | 
             
                    day: params[:day],
         | 
| 82 62 | 
             
                    time: params[:time],
         | 
| 83 63 | 
             
                    tags: tags,
         | 
| 84 | 
            -
                     | 
| 64 | 
            +
                    body: params[:body],
         | 
| 85 65 | 
             
                  )
         | 
| 86 66 |  | 
| 87 67 | 
             
                  store.write_entry(entry)
         | 
    
        data/lib/diary-ruby/version.rb
    CHANGED
    
    
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: diary-ruby
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0. | 
| 4 | 
            +
              version: 0.2.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Adam Bachman
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: exe
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2016-01- | 
| 11 | 
            +
            date: 2016-01-24 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: bundler
         | 
| @@ -122,6 +122,20 @@ dependencies: | |
| 122 122 | 
             
                - - "~>"
         | 
| 123 123 | 
             
                  - !ruby/object:Gem::Version
         | 
| 124 124 | 
             
                    version: '2.4'
         | 
| 125 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 126 | 
            +
              name: sqlite3
         | 
| 127 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 128 | 
            +
                requirements:
         | 
| 129 | 
            +
                - - "~>"
         | 
| 130 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 131 | 
            +
                    version: '1.3'
         | 
| 132 | 
            +
              type: :runtime
         | 
| 133 | 
            +
              prerelease: false
         | 
| 134 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 135 | 
            +
                requirements:
         | 
| 136 | 
            +
                - - "~>"
         | 
| 137 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 138 | 
            +
                    version: '1.3'
         | 
| 125 139 | 
             
            description: 'A command line diary: diaryrb'
         | 
| 126 140 | 
             
            email:
         | 
| 127 141 | 
             
            - adam.bachman@gmail.com
         | 
| @@ -144,9 +158,14 @@ files: | |
| 144 158 | 
             
            - exe/diaryrb
         | 
| 145 159 | 
             
            - lib/diary-ruby.rb
         | 
| 146 160 | 
             
            - lib/diary-ruby/configuration.rb
         | 
| 147 | 
            -
            - lib/diary-ruby/ | 
| 161 | 
            +
            - lib/diary-ruby/database.rb
         | 
| 162 | 
            +
            - lib/diary-ruby/database/migrator.rb
         | 
| 163 | 
            +
            - lib/diary-ruby/database/query.rb
         | 
| 164 | 
            +
            - lib/diary-ruby/ext/concern.rb
         | 
| 148 165 | 
             
            - lib/diary-ruby/ext/encryptor.rb
         | 
| 149 166 | 
             
            - lib/diary-ruby/ext/secure_pstore.rb
         | 
| 167 | 
            +
            - lib/diary-ruby/model.rb
         | 
| 168 | 
            +
            - lib/diary-ruby/models/entry.rb
         | 
| 150 169 | 
             
            - lib/diary-ruby/parser.rb
         | 
| 151 170 | 
             
            - lib/diary-ruby/server/public/script.js
         | 
| 152 171 | 
             
            - lib/diary-ruby/server/public/style.css
         | 
    
        data/lib/diary-ruby/entry.rb
    DELETED
    
    | @@ -1,85 +0,0 @@ | |
| 1 | 
            -
            TEMPLATE = "# last entry posted at %{last_update_at}
         | 
| 2 | 
            -
             | 
| 3 | 
            -
            DAY   %{day}
         | 
| 4 | 
            -
            TIME  %{time}
         | 
| 5 | 
            -
            TAGS  %{tags}
         | 
| 6 | 
            -
            TITLE %{title}
         | 
| 7 | 
            -
             | 
| 8 | 
            -
            ---
         | 
| 9 | 
            -
             | 
| 10 | 
            -
            %{text}
         | 
| 11 | 
            -
            "
         | 
| 12 | 
            -
             | 
| 13 | 
            -
            require 'rdiscount'
         | 
| 14 | 
            -
             | 
| 15 | 
            -
            module Diary
         | 
| 16 | 
            -
              class Entry
         | 
| 17 | 
            -
                attr_accessor :version, :day, :time, :tags, :text, :title, :key
         | 
| 18 | 
            -
             | 
| 19 | 
            -
                CURRENT_VERSION = 1
         | 
| 20 | 
            -
             | 
| 21 | 
            -
                def self.from_store(record)
         | 
| 22 | 
            -
                  if record[:version] == 1
         | 
| 23 | 
            -
                    self.new(
         | 
| 24 | 
            -
                      record[:version],
         | 
| 25 | 
            -
                      day: record[:day],
         | 
| 26 | 
            -
                      time: record[:time],
         | 
| 27 | 
            -
                      tags: record[:tags],
         | 
| 28 | 
            -
                      text: record[:text],
         | 
| 29 | 
            -
                      title: record[:title],
         | 
| 30 | 
            -
                      key: record[:key],
         | 
| 31 | 
            -
                    )
         | 
| 32 | 
            -
                  end
         | 
| 33 | 
            -
                end
         | 
| 34 | 
            -
             | 
| 35 | 
            -
                def self.keygen(day, time)
         | 
| 36 | 
            -
                  "%s-%s" % [day, time]
         | 
| 37 | 
            -
                end
         | 
| 38 | 
            -
             | 
| 39 | 
            -
                def self.generate(options={}, store)
         | 
| 40 | 
            -
                  options[:last_update_at] = store.read(:last_update_at)
         | 
| 41 | 
            -
             | 
| 42 | 
            -
                  # convert Arrays to dumb CSV
         | 
| 43 | 
            -
                  options.each do |(k, v)|
         | 
| 44 | 
            -
                    if v.is_a?(Array)
         | 
| 45 | 
            -
                      options[k] = v.join(', ')
         | 
| 46 | 
            -
                    end
         | 
| 47 | 
            -
                  end
         | 
| 48 | 
            -
             | 
| 49 | 
            -
                  TEMPLATE % options
         | 
| 50 | 
            -
                end
         | 
| 51 | 
            -
             | 
| 52 | 
            -
                def initialize(version, options={})
         | 
| 53 | 
            -
                  @version = version || CURRENT_VERSION
         | 
| 54 | 
            -
             | 
| 55 | 
            -
                  @day = options[:day]
         | 
| 56 | 
            -
                  @time = options[:time]
         | 
| 57 | 
            -
                  @tags = options[:tags] || []
         | 
| 58 | 
            -
                  @text = options[:text]
         | 
| 59 | 
            -
                  @title = options[:title]
         | 
| 60 | 
            -
             | 
| 61 | 
            -
                  if options[:key].nil?
         | 
| 62 | 
            -
                    @key = Entry.keygen(day, time)
         | 
| 63 | 
            -
                  else
         | 
| 64 | 
            -
                    @key = options[:key]
         | 
| 65 | 
            -
                  end
         | 
| 66 | 
            -
                end
         | 
| 67 | 
            -
             | 
| 68 | 
            -
                def formatted_text
         | 
| 69 | 
            -
                  RDiscount.new(text).to_html
         | 
| 70 | 
            -
                end
         | 
| 71 | 
            -
             | 
| 72 | 
            -
                def to_hash
         | 
| 73 | 
            -
                  {
         | 
| 74 | 
            -
                    version: version,
         | 
| 75 | 
            -
                    day: day,
         | 
| 76 | 
            -
                    time: time,
         | 
| 77 | 
            -
                    tags: tags,
         | 
| 78 | 
            -
                    text: text,
         | 
| 79 | 
            -
                    title: '',
         | 
| 80 | 
            -
                    key: key,
         | 
| 81 | 
            -
                  }
         | 
| 82 | 
            -
                end
         | 
| 83 | 
            -
              end
         | 
| 84 | 
            -
            end
         | 
| 85 | 
            -
             |