query_methods.rb 21.5 KB
Newer Older
1
require 'active_support/core_ext/array/wrap'
2 3
require 'active_support/core_ext/object/blank'

4 5
module ActiveRecord
  module QueryMethods
6 7
    extend ActiveSupport::Concern

8 9
    Relation::MULTI_VALUE_METHODS.each do |name|
      class_eval <<-CODE, __FILE__, __LINE__ + 1
10 11 12 13 14 15 16 17
        def #{name}_values                   # def select_values
          @values[:#{name}] || []            #   @values[:select] || []
        end                                  # end
                                             #
        def #{name}_values=(values)          # def select_values=(values)
          raise ImmutableRelation if @loaded #   raise ImmutableRelation if @loaded
          @values[:#{name}] = values         #   @values[:select] = values
        end                                  # end
18 19 20 21 22
      CODE
    end

    (Relation::SINGLE_VALUE_METHODS - [:create_with]).each do |name|
      class_eval <<-CODE, __FILE__, __LINE__ + 1
23 24 25
        def #{name}_value                    # def readonly_value
          @values[:#{name}]                  #   @values[:readonly]
        end                                  # end
26 27 28
      CODE
    end

29 30 31 32 33 34 35
    Relation::SINGLE_VALUE_METHODS.each do |name|
      class_eval <<-CODE, __FILE__, __LINE__ + 1
        def #{name}_value=(value)            # def readonly_value=(value)
          raise ImmutableRelation if @loaded #   raise ImmutableRelation if @loaded
          @values[:#{name}] = value          #   @values[:readonly] = value
        end                                  # end
      CODE
36 37
    end

38
    def create_with_value
39
      @values[:create_with] || {}
40
    end
41 42

    alias extensions extending_values
43

O
Oscar Del Ben 已提交
44 45 46 47 48 49 50 51 52 53 54
    # Specify relationships to be included in the result set. For
    # example:
    #
    #   users = User.includes(:address)
    #   users.each do |user|
    #     user.address.city
    #   end
    #
    # allows you to access the +address+ attribute of the +User+ model without
    # firing an additional query. This will often result in a
    # performance improvement over a simple +join+
55
    def includes(*args)
J
Jon Leighton 已提交
56
      args.empty? ? self : spawn.includes!(*args)
57
    end
58

59
    def includes!(*args)
60
      args.reject! {|a| a.blank? }
A
Aaron Patterson 已提交
61

62 63
      self.includes_values = (includes_values + args).flatten.uniq
      self
64
    end
65

66
    def eager_load(*args)
J
Jon Leighton 已提交
67
      args.blank? ? self : spawn.eager_load!(*args)
68
    end
69

70
    def eager_load!(*args)
71 72
      self.eager_load_values += args
      self
73 74 75
    end

    def preload(*args)
J
Jon Leighton 已提交
76
      args.blank? ? self : spawn.preload!(*args)
77
    end
78

79
    def preload!(*args)
80 81
      self.preload_values += args
      self
82
    end
83

84 85 86 87 88 89 90 91 92
    # Used to indicate that an association is referenced by an SQL string, and should
    # therefore be JOINed in any query rather than loaded separately.
    #
    #   User.includes(:posts).where("posts.name = 'foo'")
    #   # => Doesn't JOIN the posts table, resulting in an error.
    #
    #   User.includes(:posts).where("posts.name = 'foo'").references(:posts)
    #   # => Query now knows the string references posts, so adds a JOIN
    def references(*args)
J
Jon Leighton 已提交
93
      args.blank? ? self : spawn.references!(*args)
94
    end
95

96
    def references!(*args)
97 98 99
      args.flatten!

      self.references_values = (references_values + args.map!(&:to_s)).uniq
100
      self
101 102
    end

103
    # Works in two unique ways.
104
    #
105 106 107 108 109 110 111 112
    # First: takes a block so it can be used just like Array#select.
    #
    #   Model.scoped.select { |m| m.field == value }
    #
    # This will build an array of objects from the database for the scope,
    # converting them into an array and iterating through them using Array#select.
    #
    # Second: Modifies the SELECT statement for the query so that only certain
V
Vijay Dev 已提交
113
    # fields are retrieved:
114 115 116 117 118
    #
    #   >> Model.select(:field)
    #   => [#<Model field:value>]
    #
    # Although in the above example it looks as though this method returns an
V
Vijay Dev 已提交
119
    # array, it actually returns a relation object and can have other query
120 121
    # methods appended to it, such as the other methods in ActiveRecord::QueryMethods.
    #
122
    # The argument to the method can also be an array of fields.
123
    #
124
    #   >> Model.select([:field, :other_field, :and_one_more])
V
Vijay Dev 已提交
125
    #   => [#<Model field: "value", other_field: "value", and_one_more: "value">]
126
    #
127 128
    # Accessing attributes of an object that do not have fields retrieved by a select
    # will throw <tt>ActiveModel::MissingAttributeError</tt>:
129 130
    #
    #   >> Model.select(:field).first.other_field
131
    #   => ActiveModel::MissingAttributeError: missing attribute: other_field
132
    def select(value = Proc.new)
133
      if block_given?
134 135
        to_a.select { |*block_args| value.call(*block_args) }
      else
J
Jon Leighton 已提交
136
        spawn.select!(value)
137 138 139
      end
    end

140
    def select!(value)
141 142
      self.select_values += Array.wrap(value)
      self
143
    end
S
Santiago Pastorino 已提交
144

O
Oscar Del Ben 已提交
145 146 147 148 149
    # Allows to specify a group attribute:
    #
    #   User.group(:name)
    #   => SELECT "users".* FROM "users" GROUP BY name
    #
V
Vijay Dev 已提交
150
    # Returns an array with distinct records based on the `group` attribute:
O
Oscar Del Ben 已提交
151 152 153 154 155 156
    #
    #   User.select([:id, :name])
    #   => [#<User id: 1, name: "Oscar">, #<User id: 2, name: "Oscar">, #<User id: 3, name: "Foo">
    #
    #   User.group(:name)
    #   => [#<User id: 3, name: "Foo", ...>, #<User id: 2, name: "Oscar", ...>]
157
    def group(*args)
J
Jon Leighton 已提交
158
      args.blank? ? self : spawn.group!(*args)
159
    end
160

161
    def group!(*args)
162 163 164
      args.flatten!

      self.group_values += args
165
      self
166
    end
167

O
Oscar Del Ben 已提交
168 169 170 171 172 173 174 175 176 177
    # Allows to specify an order attribute:
    #
    #   User.order('name')
    #   => SELECT "users".* FROM "users" ORDER BY name
    #
    #   User.order('name DESC')
    #   => SELECT "users".* FROM "users" ORDER BY name DESC
    #
    #   User.order('name DESC, email')
    #   => SELECT "users".* FROM "users" ORDER BY name DESC, email
178
    def order(*args)
J
Jon Leighton 已提交
179
      args.blank? ? self : spawn.order!(*args)
180
    end
181

182
    def order!(*args)
183
      args.flatten!
184

185
      references = args.reject { |arg| Arel::Node === arg }
186
      references.map! { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }.compact!
187
      references!(references) if references.any?
188

189 190
      self.order_values += args
      self
191
    end
192

193 194 195 196 197 198 199 200 201
    # Replaces any existing order defined on the relation with the specified order.
    #
    #   User.order('email DESC').reorder('id ASC') # generated SQL has 'ORDER BY id ASC'
    #
    # Subsequent calls to order on the same relation will be appended. For example:
    #
    #   User.order('email DESC').reorder('id ASC').order('name ASC')
    #
    # generates a query with 'ORDER BY id ASC, name ASC'.
S
Sebastian Martinez 已提交
202
    def reorder(*args)
J
Jon Leighton 已提交
203
      args.blank? ? self : spawn.reorder!(*args)
204
    end
205

206
    def reorder!(*args)
207 208
      args.flatten!

209
      self.reordering_value = true
210
      self.order_values = args
211
      self
S
Sebastian Martinez 已提交
212 213
    end

214
    def joins(*args)
J
Jon Leighton 已提交
215
      args.compact.blank? ? self : spawn.joins!(*args)
216
    end
217

218
    def joins!(*args)
A
Aaron Patterson 已提交
219
      args.flatten!
220

221 222
      self.joins_values += args
      self
P
Pratik Naik 已提交
223 224
    end

A
Aaron Patterson 已提交
225
    def bind(value)
J
Jon Leighton 已提交
226
      spawn.bind!(value)
227 228
    end

229
    def bind!(value)
230 231
      self.bind_values += [value]
      self
A
Aaron Patterson 已提交
232 233
    end

234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323
    # Returns a new relation, which is the result of filtering the current relation
    # according to the conditions in the arguments.
    #
    # #where accepts conditions in one of several formats. In the examples below, the resulting
    # SQL is given as an illustration; the actual query generated may be different depending
    # on the database adapter.
    #
    # === string
    #
    # A single string, without additional arguments, is passed to the query
    # constructor as a SQL fragment, and used in the where clause of the query.
    #
    #    Client.where("orders_count = '2'")
    #    # SELECT * from clients where orders_count = '2';
    #
    # Note that building your own string from user input may expose your application
    # to injection attacks if not done properly. As an alternative, it is recommended
    # to use one of the following methods.
    #
    # === array
    #
    # If an array is passed, then the first element of the array is treated as a template, and
    # the remaining elements are inserted into the template to generate the condition.
    # Active Record takes care of building the query to avoid injection attacks, and will
    # convert from the ruby type to the database type where needed. Elements are inserted
    # into the string in the order in which they appear.
    #
    #   User.where(["name = ? and email = ?", "Joe", "joe@example.com"])
    #   # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
    #
    # Alternatively, you can use named placeholders in the template, and pass a hash as the
    # second element of the array. The names in the template are replaced with the corresponding
    # values from the hash.
    #
    #   User.where(["name = :name and email = :email", { name: "Joe", email: "joe@example.com" }])
    #   # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
    #
    # This can make for more readable code in complex queries.
    #
    # Lastly, you can use sprintf-style % escapes in the template. This works slightly differently
    # than the previous methods; you are responsible for ensuring that the values in the template
    # are properly quoted. The values are passed to the connector for quoting, but the caller
    # is responsible for ensuring they are enclosed in quotes in the resulting SQL. After quoting,
    # the values are inserted using the same escapes as the Ruby core method <tt>Kernel::sprintf</tt>.
    #
    #   User.where(["name = '%s' and email = '%s'", "Joe", "joe@example.com"])
    #   # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
    #
    # If #where is called with multiple arguments, these are treated as if they were passed as
    # the elements of a single array.
    #
    #   User.where("name = :name and email = :email", { name: "Joe", email: "joe@example.com" })
    #   # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
    #
    # When using strings to specify conditions, you can use any operator available from
    # the database. While this provides the most flexibility, you can also unintentionally introduce
    # dependencies on the underlying database. If your code is intended for general consumption,
    # test with multiple database backends.
    #
    # === hash
    #
    # #where will also accept a hash condition, in which the keys are fields and the values
    # are values to be searched for.
    #
    # Fields can be symbols or strings. Values can be single values, arrays, or ranges.
    #
    #    User.where({ name: "Joe", email: "joe@example.com" })
    #    # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com'
    #
    #    User.where({ name: ["Alice", "Bob"]})
    #    # SELECT * FROM users WHERE name IN ('Alice', 'Bob')
    #
    #    User.where({ created_at: (Time.now.midnight - 1.day)..Time.now.midnight })
    #    # SELECT * FROM users WHERE (created_at BETWEEN '2012-06-09 07:00:00.000000' AND '2012-06-10 07:00:00.000000')
    #
    # === Joins
    #
    # If the relation is the result of a join, you may create a condition which uses any of the
    # tables in the join. For string and array conditions, use the table name in the condition.
    #
    #    User.joins(:posts).where("posts.created_at < ?", Time.now)
    #
    # For hash conditions, you can either use the table name in the key, or use a sub-hash.
    #
    #    User.joins(:posts).where({ "posts.published" => true })
    #    User.joins(:posts).where({ :posts => { :published => true } })
    #
    # === empty condition
    #
    # If the condition returns true for blank?, then where is a no-op and returns the current relation.
324
    def where(opts, *rest)
J
Jon Leighton 已提交
325
      opts.blank? ? self : spawn.where!(opts, *rest)
326 327
    end

328 329 330
    # #where! is identical to #where, except that instead of returning a new relation, it adds
    # the condition to the existing relation.
    def where!(opts, *rest)
331
      references!(PredicateBuilder.references(opts)) if Hash === opts
332

333 334
      self.where_values += build_where(opts, rest)
      self
335
    end
P
Pratik Naik 已提交
336

337
    def having(opts, *rest)
J
Jon Leighton 已提交
338
      opts.blank? ? self : spawn.having!(opts, *rest)
339 340
    end

341
    def having!(opts, *rest)
342
      references!(PredicateBuilder.references(opts)) if Hash === opts
343

344 345
      self.having_values += build_where(opts, rest)
      self
346 347
    end

348
    # Specifies a limit for the number of records to retrieve.
349 350 351 352
    #
    #   User.limit(10) # generated SQL has 'LIMIT 10'
    #
    #   User.limit(10).limit(20) # generated SQL has 'LIMIT 20'
353
    def limit(value)
J
Jon Leighton 已提交
354
      spawn.limit!(value)
355 356
    end

357
    def limit!(value)
358 359
      self.limit_value = value
      self
360 361
    end

362 363 364 365
    # Specifies the number of rows to skip before returning rows.
    #
    #   User.offset(10) # generated SQL has "OFFSET 10"
    #
366
    # Should be used with order.
367
    #
368
    #   User.offset(10).order("name ASC")
369
    def offset(value)
J
Jon Leighton 已提交
370
      spawn.offset!(value)
371 372
    end

373
    def offset!(value)
374 375
      self.offset_value = value
      self
376 377 378
    end

    def lock(locks = true)
J
Jon Leighton 已提交
379
      spawn.lock!(locks)
380
    end
381

382
    def lock!(locks = true)
383
      case locks
384
      when String, TrueClass, NilClass
385
        self.lock_value = locks || true
386
      else
387
        self.lock_value = false
388
      end
389

390
      self
391 392
    end

393 394 395 396 397
    # Returns a chainable relation with zero records, specifically an
    # instance of the NullRelation class.
    #
    # The returned NullRelation inherits from Relation and implements the
    # Null Object pattern so it is an object with defined null behavior:
398
    # it always returns an empty array of records and does not query the database.
399 400 401 402
    #
    # Any subsequent condition chained to the returned relation will continue
    # generating an empty relation and will not fire any query to the database.
    #
403 404
    # Used in cases where a method or scope could return zero records but the
    # result needs to be chainable.
405 406 407 408
    #
    # For example:
    #
    #   @posts = current_user.visible_posts.where(:name => params[:name])
409
    #   # => the visible_posts method is expected to return a chainable Relation
410 411 412
    #
    #   def visible_posts
    #     case role
413
    #     when 'Country Manager'
414
    #       Post.where(:country => country)
415
    #     when 'Reviewer'
416
    #       Post.published
417
    #     when 'Bad User'
418 419 420 421 422
    #       Post.none # => returning [] instead breaks the previous code
    #     end
    #   end
    #
    def none
A
Akira Matsuda 已提交
423
      scoped.extending(NullRelation)
424 425
    end

426
    def readonly(value = true)
J
Jon Leighton 已提交
427
      spawn.readonly!(value)
428 429
    end

430
    def readonly!(value = true)
431 432
      self.readonly_value = value
      self
433 434
    end

435
    def create_with(value)
J
Jon Leighton 已提交
436
      spawn.create_with!(value)
437 438
    end

439
    def create_with!(value)
440 441
      self.create_with_value = value ? create_with_value.merge(value) : {}
      self
442 443
    end

444 445 446 447 448 449 450
    # Specifies table from which the records will be fetched. For example:
    #
    #   Topic.select('title').from('posts')
    #   #=> SELECT title FROM posts
    #
    # Can accept other relation objects. For example:
    #
451
    #   Topic.select('title').from(Topic.approved)
452 453
    #   # => SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery
    #
454
    #   Topic.select('a.title').from(Topic.approved, :a)
455 456 457 458
    #   # => SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a
    #
    def from(value, subquery_name = nil)
      spawn.from!(value, subquery_name)
459 460
    end

461
    def from!(value, subquery_name = nil)
462
      self.from_value = [value, subquery_name]
463
      self
464 465
    end

466 467 468 469 470 471 472 473 474 475 476
    # Specifies whether the records should be unique or not. For example:
    #
    #   User.select(:name)
    #   # => Might return two records with the same name
    #
    #   User.select(:name).uniq
    #   # => Returns 1 record per unique name
    #
    #   User.select(:name).uniq.uniq(false)
    #   # => You can also remove the uniqueness
    def uniq(value = true)
J
Jon Leighton 已提交
477
      spawn.uniq!(value)
478 479
    end

480
    def uniq!(value = true)
481 482
      self.uniq_value = value
      self
483 484
    end

485
    # Used to extend a scope with additional methods, either through
486 487
    # a module or through a block provided.
    #
488 489 490 491 492 493 494 495 496 497 498 499 500
    # The object returned is a relation, which can be further extended.
    #
    # === Using a module
    #
    #   module Pagination
    #     def page(number)
    #       # pagination code goes here
    #     end
    #   end
    #
    #   scope = Model.scoped.extending(Pagination)
    #   scope.page(params[:page])
    #
V
Vijay Dev 已提交
501
    # You can also pass a list of modules:
502 503 504 505 506 507 508
    #
    #   scope = Model.scoped.extending(Pagination, SomethingElse)
    #
    # === Using a block
    #
    #   scope = Model.scoped.extending do
    #     def page(number)
509
    #       # pagination code goes here
510 511 512 513 514 515 516 517
    #     end
    #   end
    #   scope.page(params[:page])
    #
    # You can also use a block and a module list:
    #
    #   scope = Model.scoped.extending(Pagination) do
    #     def per_page(number)
518
    #       # pagination code goes here
519 520
    #     end
    #   end
521 522
    def extending(*modules, &block)
      if modules.any? || block
J
Jon Leighton 已提交
523
        spawn.extending!(*modules, &block)
524 525 526 527
      else
        self
      end
    end
528

529
    def extending!(*modules, &block)
530
      modules << Module.new(&block) if block_given?
531

532
      self.extending_values = modules.flatten
533
      extend(*extending_values) if extending_values.any?
534

535
      self
536 537
    end

538 539 540
    # Reverse the existing order clause on the relation.
    #
    #   User.order('name ASC').reverse_order # generated SQL has 'ORDER BY name DESC'
541
    def reverse_order
J
Jon Leighton 已提交
542
      spawn.reverse_order!
543 544
    end

545
    def reverse_order!
546 547
      self.reverse_order_value = !reverse_order_value
      self
548 549
    end

550
    def arel
551
      @arel ||= with_default_scope.build_arel
552 553
    end

554
    def build_arel
555
      arel = table.from table
556

557
      build_joins(arel, joins_values) unless joins_values.empty?
558

559
      collapse_wheres(arel, (where_values - ['']).uniq)
560

561
      arel.having(*having_values.uniq.reject{|h| h.blank?}) unless having_values.empty?
562

563 564
      arel.take(connection.sanitize_limit(limit_value)) if limit_value
      arel.skip(offset_value.to_i) if offset_value
A
Aaron Patterson 已提交
565

566
      arel.group(*group_values.uniq.reject{|g| g.blank?}) unless group_values.empty?
567

568 569
      order = order_values
      order = reverse_sql_order(order) if reverse_order_value
570
      arel.order(*order.uniq.reject{|o| o.blank?}) unless order.empty?
571

572
      build_select(arel, select_values.uniq)
573

574
      arel.distinct(uniq_value)
575
      arel.from(build_from) if from_value
576
      arel.lock(lock_value) if lock_value
577 578

      arel
579 580
    end

581 582
    private

583
    def custom_join_ast(table, joins)
584 585
      joins = joins.reject { |join| join.blank? }

586
      return [] if joins.empty?
587 588 589

      @implicit_readonly = true

590
      joins.map do |join|
591 592 593 594 595 596
        case join
        when Array
          join = Arel.sql(join.join(' ')) if array_of_strings?(join)
        when String
          join = Arel.sql(join)
        end
597
        table.create_string_join(join)
598 599 600
      end
    end

601 602 603
    def collapse_wheres(arel, wheres)
      equalities = wheres.grep(Arel::Nodes::Equality)

A
Aaron Patterson 已提交
604
      arel.where(Arel::Nodes::And.new(equalities)) unless equalities.empty?
605 606 607

      (wheres - equalities).each do |where|
        where = Arel.sql(where) if String === where
608
        arel.where(Arel::Nodes::Grouping.new(where))
609 610 611
      end
    end

612
    def build_where(opts, other = [])
A
Aaron Patterson 已提交
613 614
      case opts
      when String, Array
615
        [@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
A
Aaron Patterson 已提交
616
      when Hash
617
        PredicateBuilder.build_from_hash(table.engine, opts, table)
618
      else
619
        [opts]
620 621 622
      end
    end

623 624 625 626 627 628 629 630 631 632 633
    def build_from
      opts, name = from_value
      case opts
      when Relation
        name ||= 'subquery'
        opts.arel.as(name.to_s)
      else
        opts
      end
    end

634
    def build_joins(manager, joins)
A
Aaron Patterson 已提交
635 636 637 638 639 640
      buckets = joins.group_by do |join|
        case join
        when String
          'string_join'
        when Hash, Symbol, Array
          'association_join'
641
        when ActiveRecord::Associations::JoinDependency::JoinAssociation
A
Aaron Patterson 已提交
642
          'stashed_join'
643 644
        when Arel::Nodes::Join
          'join_node'
A
Aaron Patterson 已提交
645 646 647
        else
          raise 'unknown class: %s' % join.class.name
        end
648 649
      end

A
Aaron Patterson 已提交
650 651
      association_joins         = buckets['association_join'] || []
      stashed_association_joins = buckets['stashed_join'] || []
652
      join_nodes                = (buckets['join_node'] || []).uniq
A
Aaron Patterson 已提交
653 654 655
      string_joins              = (buckets['string_join'] || []).map { |x|
        x.strip
      }.uniq
656

657
      join_list = join_nodes + custom_join_ast(manager, string_joins)
658

659
      join_dependency = ActiveRecord::Associations::JoinDependency.new(
660 661 662 663
        @klass,
        association_joins,
        join_list
      )
664 665 666 667 668

      join_dependency.graft(*stashed_association_joins)

      @implicit_readonly = true unless association_joins.empty? && stashed_association_joins.empty?

A
Aaron Patterson 已提交
669
      # FIXME: refactor this to build an AST
670
      join_dependency.join_associations.each do |association|
671
        association.join_to(manager)
672 673
      end

674
      manager.join_sources.concat join_list
675 676

      manager
677 678
    end

679
    def build_select(arel, selects)
680
      unless selects.empty?
681
        @implicit_readonly = false
682
        arel.project(*selects)
683
      else
684
        arel.project(@klass.arel_table[Arel.star])
685 686 687
      end
    end

688
    def reverse_sql_order(order_query)
B
Brian Mathiyakom 已提交
689 690
      order_query = ["#{quoted_table_name}.#{quoted_primary_key} ASC"] if order_query.empty?

691 692
      order_query.map do |o|
        case o
693
        when Arel::Nodes::Ordering
694 695
          o.reverse
        when String, Symbol
696 697 698 699
          o.to_s.split(',').collect do |s|
            s.strip!
            s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC')
          end
700 701 702 703
        else
          o
        end
      end.flatten
704 705
    end

P
Pratik Naik 已提交
706 707 708 709
    def array_of_strings?(o)
      o.is_a?(Array) && o.all?{|obj| obj.is_a?(String)}
    end

710 711
  end
end