Class: Lllibrary::DSL

Inherits:
Object
  • Object
show all
Defined in:
lib/lllibrary/dsl.rb

Overview

Contains all the selectors used in Lllibrary’s DSL. See Lllibrary#select for how to access the DSL.

Instance Method Summary collapse

Constructor Details

#initialize(library) ⇒ DSL

Returns a new instance of DSL.



5
6
7
# File 'lib/lllibrary/dsl.rb', line 5

def initialize(library)
  @library = library
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(field, *args) ⇒ Object

Turns all the columns on the tracks table into methods that I call selectors. Based on the column’s type in the database, this either delegates to DSL#numeric_selector or DSL#string_selector.



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# File 'lib/lllibrary/dsl.rb', line 12

def method_missing(field, *args)
  if Lllibrary::Track.column_names.include? field.to_s
    type = Lllibrary::Track.columns_hash[field.to_s].type
    case type
    when :integer, :float, :decimal, :boolean, :datetime, :timestamp, :date, :time
      numeric_selector(field, *args)
    when :string, :text
      string_selector(field, *args)
    else
      raise ArgumentError, "lllibrary: selectors aren't supported for #{field}'s type '#{type}'"
    end
  else
    super
  end
end

Instance Method Details

#allObject

Selects all the tracks in the library.



29
30
31
# File 'lib/lllibrary/dsl.rb', line 29

def all
  @library.tracks.all
end

#noneObject

Selects no tracks, returning an empty playlist.



34
35
36
# File 'lib/lllibrary/dsl.rb', line 34

def none
  []
end

#numeric_selector(column, *values_or_hash) ⇒ Object

Returns an Array of tracks that satisfy certain conditions on the given numeric column. You can pass an exact value to check for equality, a range, or a hash that specifies greater-than and less-than operators.

The possible operators, with their shortcuts, are:

:greater_than / :gt
:less_than / :lt
:greater_than_or_equal / :gte
:less_than_or_equal / :lte
:not_equal / :ne

Note: You can only use one of these operators at a time. If you want a range, use a Range.

Time strings like “1:23” can be given, and will be converted to a Range of milliseconds. For example, “1:00” will be treated like 60000..60999. Obviously, any field this is used on should be storing time in milliseconds.

Here’s some examples:

year(2010..2012)           # equivalent to year(2010, 2011, 2012)
year(2011)                 # only matches 2011
year(gte: 2000)            # matches 2000 and up
total_time("2:30")         # matches 150000..150999 milliseconds
total_time("1:00".."2:00") # matches 60000..120999 milliseconds


98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/lllibrary/dsl.rb', line 98

def numeric_selector(column, *values_or_hash)
  parse = lambda do |x|
    if x.is_a? String
      ms = Lllibrary.parse_time(x)
      if ms % 1000 == 0
        ms...(ms + 1000)
      else
        ms
      end
    elsif x.is_a? Range
      left = x.begin.is_a?(String) ? Lllibrary.parse_time(x.begin) : x.begin
      right = x.end.is_a?(String) ? Lllibrary.parse_time(x.end) : x.end
      right += 999 if right % 1000 == 0 && !x.exclude_end?
      Range.new(left, right, x.exclude_end?)
    else
      x
    end
  end

  if values_or_hash.last.is_a? Hash
    operator = values_or_hash.last.keys.first.to_sym
    value = parse.(values_or_hash.last.values.first)
    case operator
    when :greater_than, :gt
      op = ">"
      if value.is_a? Range
        op = ">=" if value.exclude_end?
        value = value.end
      end
      @library.tracks.where("tracks.#{column} #{op} ?", value).all
    when :less_than, :lt
      value = value.begin if value.is_a? Range
      @library.tracks.where("tracks.#{column} < ?", value).all
    when :greater_than_or_equal, :gte
      value = value.begin if value.is_a? Range
      @library.tracks.where("tracks.#{column} >= ?", value).all
    when :less_than_or_equal, :lte
      op = "<="
      if value.is_a? Range
        op = "<" if value.exclude_end?
        value = value.end
      end
      @library.tracks.where("tracks.#{column} #{op} ?", value)
    when :not_equal, :ne
      if value.is_a? Range
        op = value.exclude_end? ? ">=" : ">"
        @library.tracks.where("tracks.#{column} < ? AND tracks.#{column} #{op} ?", value.begin, value.end)
      else
        @library.tracks.where("tracks.#{column} != ?", value)
      end
    else
      raise ArgumentError, "'#{operator}' isn't a valid operator"
    end
  else
    values_or_hash.map do |value|
      @library.tracks.where(column => parse.(value)).all
    end.flatten
  end
end

#playlist(*names) ⇒ Object

Returns an Array of all tracks that are in a Playlist whose name matches one of the strings given to this selector. It’s kind of ugly, I will probably be changing this.



161
162
163
164
165
166
167
168
# File 'lib/lllibrary/dsl.rb', line 161

def playlist(*names)
  names.map do |name|
    playlists = @library.playlists.arel_table
    if playlist = @library.playlists.where(playlists[:name].matches(name.to_s)).first
      playlist.tracks.all
    end
  end.flatten
end

#string_selector(column, *queries) ⇒ Object

Returns an Array of tracks that match the string query on the given column. It’s SQL underneath, so you can use % and _ as wildcards in the query. By default, % wildcards are inserted on the left and right of your query. Use the :match option to change this:

:match => :middle   "%query%"   (default)
:match => :left     "query%"
:match => :right    "%query"
:match => :exact    "query"

Here are some examples:

genre(:electronic, :edm)    # matches "Electronic", "Electronica", "EDM", etc.
genre(:tmbg, match: :exact) # only matches "TMBG" (but case-insensitively)
composer(:rachmanino, match: :left) # matches "Rachmaninov", "Rachmaninoff", but not "Sergei Rachmaninov"


54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/lllibrary/dsl.rb', line 54

def string_selector(column, *queries)
  options = queries.last.is_a?(Hash) ? queries.pop : {}
  options[:match] ||= :middle

  tracks = @library.tracks.arel_table
  queries.map do |query|
    query = {
      exact: "#{query}",
      left: "#{query}%",
      right: "%#{query}",
      middle: "%#{query}%"
    }[options[:match]]

    @library.tracks.where(tracks[column].matches(query)).all
  end.flatten
end