mirror of
https://github.com/github/codeql.git
synced 2026-04-28 10:15:14 +02:00
Merge branch 'main' into refacReDoS
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Calls to methods generated by ActiveRecord associations are now recognised as
|
||||
instantiations of ActiveRecord objects. This increases the sensitivity of
|
||||
queries such as `rb/sql-injection` and `rb/stored-xss`.
|
||||
@@ -348,7 +348,7 @@ module ExprNodes {
|
||||
/** Gets an argument of this call. */
|
||||
final ExprCfgNode getAnArgument() { result = this.getArgument(_) }
|
||||
|
||||
/** Gets the the keyword argument whose key is `keyword` of this call. */
|
||||
/** Gets the keyword argument whose key is `keyword` of this call. */
|
||||
final ExprCfgNode getKeywordArgument(string keyword) {
|
||||
exists(PairCfgNode n |
|
||||
e.hasCfgChild(e.getAnArgument(), this, n) and
|
||||
|
||||
@@ -79,7 +79,7 @@ class CallNode extends LocalSourceNode, ExprNode {
|
||||
result.getExprNode() = node.getPositionalArgument(n)
|
||||
}
|
||||
|
||||
/** Gets the name of the the method called by the method call (if any) corresponding to this data-flow node */
|
||||
/** Gets the name of the method called by the method call (if any) corresponding to this data-flow node */
|
||||
string getMethodName() { result = node.getExpr().(MethodCall).getMethodName() }
|
||||
|
||||
/** Gets the number of arguments of this call. */
|
||||
|
||||
@@ -96,7 +96,7 @@ private module Cached {
|
||||
or
|
||||
FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom, nodeTo, false)
|
||||
or
|
||||
// Although flow through collections is modelled precisely using stores/reads, we still
|
||||
// Although flow through collections is modeled precisely using stores/reads, we still
|
||||
// allow flow out of a _tainted_ collection. This is needed in order to support taint-
|
||||
// tracking configurations where the source is a collection.
|
||||
exists(DataFlow::ContentSet c | readStep(nodeFrom, c, nodeTo) |
|
||||
|
||||
@@ -516,3 +516,177 @@ private module Persistence {
|
||||
override DataFlow::Node getValue() { assignNode.getRhs() = result.asExpr() }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A method call inside an ActiveRecord model class that establishes an
|
||||
* association between this model and another model.
|
||||
*
|
||||
* ```rb
|
||||
* class User
|
||||
* has_many :posts
|
||||
* has_one :profile
|
||||
* end
|
||||
* ```
|
||||
*/
|
||||
private class ActiveRecordAssociation extends DataFlow::CallNode {
|
||||
private ActiveRecordModelClass modelClass;
|
||||
|
||||
ActiveRecordAssociation() {
|
||||
not exists(this.asExpr().getExpr().getEnclosingMethod()) and
|
||||
this.asExpr().getExpr().getEnclosingModule() = modelClass and
|
||||
this.getMethodName() = ["has_one", "has_many", "belongs_to", "has_and_belongs_to_many"]
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the class which declares this association.
|
||||
* For example, in
|
||||
* ```rb
|
||||
* class User
|
||||
* has_many :posts
|
||||
* end
|
||||
* ```
|
||||
* the source class is `User`.
|
||||
*/
|
||||
ActiveRecordModelClass getSourceClass() { result = modelClass }
|
||||
|
||||
/**
|
||||
* Gets the class which this association refers to.
|
||||
* For example, in
|
||||
* ```rb
|
||||
* class User
|
||||
* has_many :posts
|
||||
* end
|
||||
* ```
|
||||
* the target class is `Post`.
|
||||
*/
|
||||
ActiveRecordModelClass getTargetClass() {
|
||||
result.getName().toLowerCase() = this.getTargetModelName()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the (lowercase) name of the model this association targets.
|
||||
* For example, in `has_many :posts`, this is `post`.
|
||||
*/
|
||||
string getTargetModelName() {
|
||||
exists(string s |
|
||||
s = this.getArgument(0).asExpr().getExpr().getConstantValue().getStringlikeValue()
|
||||
|
|
||||
// has_one :profile
|
||||
// belongs_to :user
|
||||
this.isSingular() and
|
||||
result = s
|
||||
or
|
||||
// has_many :posts
|
||||
// has_many :stories
|
||||
this.isCollection() and
|
||||
pluralize(result) = s
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if this association is one-to-one */
|
||||
predicate isSingular() { this.getMethodName() = ["has_one", "belongs_to"] }
|
||||
|
||||
/** Holds if this association is one-to-many or many-to-many */
|
||||
predicate isCollection() { this.getMethodName() = ["has_many", "has_and_belongs_to_many"] }
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts `input` to plural form.
|
||||
*/
|
||||
bindingset[input]
|
||||
bindingset[result]
|
||||
private string pluralize(string input) {
|
||||
exists(string stem | stem + "y" = input | result = stem + "ies")
|
||||
or
|
||||
result = input + "s"
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to a method generated by an ActiveRecord association.
|
||||
* These yield ActiveRecord collection proxies, which act like collections but
|
||||
* add some additional methods.
|
||||
* We exclude `<model>_changed?` and `<model>_previously_changed?` because these
|
||||
* do not yield ActiveRecord instances.
|
||||
* https://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
|
||||
*/
|
||||
private class ActiveRecordAssociationMethodCall extends DataFlow::CallNode {
|
||||
ActiveRecordAssociation assoc;
|
||||
|
||||
ActiveRecordAssociationMethodCall() {
|
||||
exists(string model | model = assoc.getTargetModelName() |
|
||||
this.getReceiver().(ActiveRecordInstance).getClass() = assoc.getSourceClass() and
|
||||
(
|
||||
assoc.isCollection() and
|
||||
(
|
||||
this.getMethodName() = pluralize(model) + ["", "="]
|
||||
or
|
||||
this.getMethodName() = "<<"
|
||||
or
|
||||
this.getMethodName() = model + ["_ids", "_ids="]
|
||||
)
|
||||
or
|
||||
assoc.isSingular() and
|
||||
(
|
||||
this.getMethodName() = model + ["", "="] or
|
||||
this.getMethodName() = ["build_", "reload_"] + model or
|
||||
this.getMethodName() = "create_" + model + ["!", ""]
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
ActiveRecordAssociation getAssociation() { result = assoc }
|
||||
}
|
||||
|
||||
/**
|
||||
* A method call on an ActiveRecord collection proxy that yields one or more
|
||||
* ActiveRecord instances.
|
||||
* Example:
|
||||
* ```rb
|
||||
* class User < ActiveRecord::Base
|
||||
* has_many :posts
|
||||
* end
|
||||
*
|
||||
* User.new.posts.create
|
||||
* ```
|
||||
* https://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
|
||||
*/
|
||||
private class ActiveRecordCollectionProxyMethodCall extends DataFlow::CallNode {
|
||||
ActiveRecordCollectionProxyMethodCall() {
|
||||
this.getMethodName() =
|
||||
[
|
||||
"push", "concat", "build", "create", "create!", "delete", "delete_all", "destroy",
|
||||
"destroy_all", "find", "distinct", "reset", "reload"
|
||||
] and
|
||||
(
|
||||
this.getReceiver().(ActiveRecordAssociationMethodCall).getAssociation().isCollection()
|
||||
or
|
||||
exists(ActiveRecordCollectionProxyMethodCall receiver | receiver = this.getReceiver() |
|
||||
receiver.getAssociation().isCollection() and
|
||||
receiver.getMethodName() = ["reset", "reload", "distinct"]
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
ActiveRecordAssociation getAssociation() {
|
||||
result = this.getReceiver().(ActiveRecordAssociationMethodCall).getAssociation()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to an association method which yields ActiveRecord instances.
|
||||
*/
|
||||
private class ActiveRecordAssociationModelInstantiation extends ActiveRecordModelInstantiation instanceof ActiveRecordAssociationMethodCall {
|
||||
override ActiveRecordModelClass getClass() {
|
||||
result = this.(ActiveRecordAssociationMethodCall).getAssociation().getTargetClass()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to a method on a collection proxy which yields ActiveRecord instances.
|
||||
*/
|
||||
private class ActiveRecordCollectionProxyModelInstantiation extends ActiveRecordModelInstantiation instanceof ActiveRecordCollectionProxyMethodCall {
|
||||
override ActiveRecordModelClass getClass() {
|
||||
result = this.(ActiveRecordCollectionProxyMethodCall).getAssociation().getTargetClass()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ module ActiveSupport {
|
||||
* Flow summary for methods which transform the receiver in some way, possibly preserving taint.
|
||||
*/
|
||||
private class StringTransformSummary extends SummarizedCallable {
|
||||
// We're modelling a lot of different methods, so we make up a name for this summary.
|
||||
// We're modeling a lot of different methods, so we make up a name for this summary.
|
||||
StringTransformSummary() { this = "ActiveSupportStringTransform" }
|
||||
|
||||
override MethodCall getACall() {
|
||||
|
||||
@@ -379,7 +379,7 @@ class GraphqlFieldResolutionMethod extends Method, HTTP::Server::RequestHandler:
|
||||
result.(KeywordParameter).hasName(argDefn.getArgumentName())
|
||||
or
|
||||
// TODO this will cause false positives because now *anything* in the **args
|
||||
// param will be flagged as as RoutedParameter/RemoteFlowSource, but really
|
||||
// param will be flagged as RoutedParameter/RemoteFlowSource, but really
|
||||
// only the hash keys corresponding to the defined arguments are user input
|
||||
// others could be things defined in the `:extras` keyword argument to the `argument`
|
||||
result instanceof HashSplatParameter // often you see `def field(**args)`
|
||||
|
||||
@@ -16,65 +16,44 @@ private import codeql.ruby.dataflow.internal.DataFlowDispatch
|
||||
* in `Array.qll`.
|
||||
*/
|
||||
module Hash {
|
||||
// cannot use API graphs due to negative recursion
|
||||
private predicate isHashLiteralPair(Pair pair, ConstantValue key) {
|
||||
key = DataFlow::Content::getKnownElementIndex(pair.getKey()) and
|
||||
pair = any(MethodCall mc | mc.getMethodName() = "[]").getAnArgument()
|
||||
}
|
||||
|
||||
private class HashLiteralSymbolSummary extends SummarizedCallable {
|
||||
private ConstantValue::ConstantSymbolValue symbol;
|
||||
|
||||
HashLiteralSymbolSummary() {
|
||||
isHashLiteralPair(_, symbol) and
|
||||
this = "Hash.[" + symbol.serialize() + "]"
|
||||
}
|
||||
|
||||
final override MethodCall getACall() {
|
||||
result = API::getTopLevelMember("Hash").getAMethodCall("[]").getExprNode().getExpr() and
|
||||
exists(result.getKeywordArgument(symbol.getSymbol()))
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
// { symbol: x }
|
||||
input = "Argument[" + symbol.getSymbol() + ":]" and
|
||||
output = "ReturnValue.Element[" + symbol.serialize() + "]" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private class HashLiteralNonSymbolSummary extends SummarizedCallable {
|
||||
private ConstantValue key;
|
||||
|
||||
HashLiteralNonSymbolSummary() {
|
||||
this = "Hash.[]" and
|
||||
isHashLiteralPair(_, key) and
|
||||
/**
|
||||
* Holds if `key` is used as the non-symbol key in a hash literal. For example
|
||||
*
|
||||
* ```rb
|
||||
* {
|
||||
* :a => 1, # symbol
|
||||
* "b" => 2 # non-symbol, "b" is the key
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
private predicate isHashLiteralNonSymbolKey(ConstantValue key) {
|
||||
exists(Pair pair |
|
||||
key = DataFlow::Content::getKnownElementIndex(pair.getKey()) and
|
||||
// cannot use API graphs due to negative recursion
|
||||
pair = any(MethodCall mc | mc.getMethodName() = "[]").getAnArgument() and
|
||||
not key.isSymbol(_)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private class HashLiteralSummary extends SummarizedCallable {
|
||||
HashLiteralSummary() { this = "Hash.[]" }
|
||||
|
||||
final override MethodCall getACall() {
|
||||
result = API::getTopLevelMember("Hash").getAMethodCall("[]").getExprNode().getExpr() and
|
||||
isHashLiteralPair(result.getAnArgument(), key)
|
||||
result = API::getTopLevelMember("Hash").getAMethodCall("[]").getExprNode().getExpr()
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
// { 'nonsymbol' => x }
|
||||
input = "Argument[0..].PairValue[" + key.serialize() + "]" and
|
||||
output = "ReturnValue.Element[" + key.serialize() + "]" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
private class HashLiteralHashSplatSummary extends SummarizedCallable {
|
||||
HashLiteralHashSplatSummary() { this = "Hash.[**]" }
|
||||
|
||||
final override MethodCall getACall() {
|
||||
result = API::getTopLevelMember("Hash").getAMethodCall("[]").getExprNode().getExpr() and
|
||||
result.getAnArgument() instanceof HashSplatExpr
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
// { **hash }
|
||||
exists(ConstantValue key |
|
||||
isHashLiteralNonSymbolKey(key) and
|
||||
input = "Argument[0..].PairValue[" + key.serialize() + "]" and
|
||||
output = "ReturnValue.Element[" + key.serialize() + "]" and
|
||||
preservesValue = true
|
||||
)
|
||||
or
|
||||
// { symbol: x }
|
||||
// we make use of the special `hash-splat` argument kind, which contains all keyword
|
||||
// arguments wrapped in an implicit hash, as well as explicit hash splat arguments
|
||||
input = "Argument[hash-splat].WithElement[any]" and
|
||||
output = "ReturnValue" and
|
||||
preservesValue = true
|
||||
|
||||
@@ -40,7 +40,7 @@ private import codeql.ruby.dataflow.internal.DataFlowDispatch as DataFlowDispatc
|
||||
*/
|
||||
bindingset[package]
|
||||
predicate isPackageUsed(string package) {
|
||||
// For now everything is modelled as an access path starting at any top-level, so the package name has no effect.
|
||||
// For now everything is modeled as an access path starting at any top-level, so the package name has no effect.
|
||||
//
|
||||
// We allow an arbitrary package name so that the model can record the name of the package in case it's needed in the future.
|
||||
//
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -68,6 +68,14 @@ def m3()
|
||||
hash4 = Hash[:a, taint(3.4), :b, 1]
|
||||
sink(hash4[:a]) # $ hasValueFlow=3.4
|
||||
sink(hash4[:b])
|
||||
|
||||
hash5 = Hash["a" => taint(3.5), "b" => 1]
|
||||
sink(hash5["a"]) # $ hasValueFlow=3.5
|
||||
sink(hash5["b"])
|
||||
|
||||
hash6 = Hash[{"a" => taint(3.6), "b" => 1}]
|
||||
sink(hash6["a"]) # $ hasValueFlow=3.6
|
||||
sink(hash6["b"])
|
||||
end
|
||||
|
||||
m3()
|
||||
|
||||
@@ -2,15 +2,102 @@ activeRecordModelClasses
|
||||
| ActiveRecord.rb:1:1:3:3 | UserGroup |
|
||||
| ActiveRecord.rb:5:1:15:3 | User |
|
||||
| ActiveRecord.rb:17:1:21:3 | Admin |
|
||||
| associations.rb:1:1:3:3 | Author |
|
||||
| associations.rb:5:1:9:3 | Post |
|
||||
| associations.rb:11:1:13:3 | Tag |
|
||||
| associations.rb:15:1:17:3 | Comment |
|
||||
activeRecordInstances
|
||||
| ActiveRecord.rb:9:5:9:68 | call to find |
|
||||
| ActiveRecord.rb:13:5:13:40 | call to find_by |
|
||||
| ActiveRecord.rb:13:5:13:46 | call to users |
|
||||
| ActiveRecord.rb:36:5:36:30 | call to find_by_name |
|
||||
| ActiveRecord.rb:55:5:57:7 | if ... |
|
||||
| ActiveRecord.rb:55:43:56:40 | then ... |
|
||||
| ActiveRecord.rb:56:7:56:40 | call to find_by |
|
||||
| ActiveRecord.rb:60:5:60:33 | call to find_by |
|
||||
| ActiveRecord.rb:62:5:62:34 | call to find |
|
||||
| associations.rb:19:1:19:20 | ... = ... |
|
||||
| associations.rb:19:1:19:20 | ... = ... |
|
||||
| associations.rb:19:11:19:20 | call to new |
|
||||
| associations.rb:21:1:21:28 | ... = ... |
|
||||
| associations.rb:21:1:21:28 | ... = ... |
|
||||
| associations.rb:21:9:21:15 | author1 |
|
||||
| associations.rb:21:9:21:21 | call to posts |
|
||||
| associations.rb:21:9:21:28 | call to create |
|
||||
| associations.rb:23:1:23:32 | ... = ... |
|
||||
| associations.rb:23:12:23:16 | post1 |
|
||||
| associations.rb:23:12:23:25 | call to comments |
|
||||
| associations.rb:23:12:23:32 | call to create |
|
||||
| associations.rb:25:1:25:22 | ... = ... |
|
||||
| associations.rb:25:1:25:22 | ... = ... |
|
||||
| associations.rb:25:11:25:15 | post1 |
|
||||
| associations.rb:25:11:25:22 | call to author |
|
||||
| associations.rb:27:1:27:28 | ... = ... |
|
||||
| associations.rb:27:1:27:28 | ... = ... |
|
||||
| associations.rb:27:9:27:15 | author2 |
|
||||
| associations.rb:27:9:27:21 | call to posts |
|
||||
| associations.rb:27:9:27:28 | call to create |
|
||||
| associations.rb:29:1:29:7 | author2 |
|
||||
| associations.rb:29:1:29:13 | call to posts |
|
||||
| associations.rb:29:1:29:22 | ... << ... |
|
||||
| associations.rb:29:18:29:22 | post2 |
|
||||
| associations.rb:31:1:31:5 | post1 |
|
||||
| associations.rb:31:1:31:12 | __synth__0 |
|
||||
| associations.rb:31:1:31:12 | call to author= |
|
||||
| associations.rb:31:1:31:22 | ... |
|
||||
| associations.rb:31:16:31:22 | ... = ... |
|
||||
| associations.rb:31:16:31:22 | ... = ... |
|
||||
| associations.rb:31:16:31:22 | author2 |
|
||||
| associations.rb:35:1:35:5 | post2 |
|
||||
| associations.rb:35:1:35:14 | call to comments |
|
||||
| associations.rb:35:1:35:21 | call to create |
|
||||
| associations.rb:37:1:37:7 | author1 |
|
||||
| associations.rb:37:1:37:13 | call to posts |
|
||||
| associations.rb:37:1:37:20 | call to reload |
|
||||
| associations.rb:37:1:37:27 | call to create |
|
||||
| associations.rb:39:1:39:5 | post1 |
|
||||
| associations.rb:40:1:40:5 | post1 |
|
||||
| associations.rb:42:1:42:7 | author1 |
|
||||
| associations.rb:42:1:42:13 | call to posts |
|
||||
| associations.rb:42:1:42:25 | call to push |
|
||||
| associations.rb:42:20:42:24 | post2 |
|
||||
| associations.rb:43:1:43:7 | author1 |
|
||||
| associations.rb:43:1:43:13 | call to posts |
|
||||
| associations.rb:43:1:43:27 | call to concat |
|
||||
| associations.rb:43:22:43:26 | post2 |
|
||||
| associations.rb:44:1:44:7 | author1 |
|
||||
| associations.rb:44:1:44:13 | call to posts |
|
||||
| associations.rb:44:1:44:19 | call to build |
|
||||
| associations.rb:45:1:45:7 | author1 |
|
||||
| associations.rb:45:1:45:13 | call to posts |
|
||||
| associations.rb:45:1:45:20 | call to create |
|
||||
| associations.rb:46:1:46:7 | author1 |
|
||||
| associations.rb:46:1:46:13 | call to posts |
|
||||
| associations.rb:46:1:46:21 | call to create! |
|
||||
| associations.rb:47:1:47:7 | author1 |
|
||||
| associations.rb:47:1:47:13 | call to posts |
|
||||
| associations.rb:47:1:47:20 | call to delete |
|
||||
| associations.rb:48:1:48:7 | author1 |
|
||||
| associations.rb:48:1:48:13 | call to posts |
|
||||
| associations.rb:48:1:48:24 | call to delete_all |
|
||||
| associations.rb:49:1:49:7 | author1 |
|
||||
| associations.rb:49:1:49:13 | call to posts |
|
||||
| associations.rb:49:1:49:21 | call to destroy |
|
||||
| associations.rb:50:1:50:7 | author1 |
|
||||
| associations.rb:50:1:50:13 | call to posts |
|
||||
| associations.rb:50:1:50:25 | call to destroy_all |
|
||||
| associations.rb:51:1:51:7 | author1 |
|
||||
| associations.rb:51:1:51:13 | call to posts |
|
||||
| associations.rb:51:1:51:22 | call to distinct |
|
||||
| associations.rb:51:1:51:36 | call to find |
|
||||
| associations.rb:52:1:52:7 | author1 |
|
||||
| associations.rb:52:1:52:13 | call to posts |
|
||||
| associations.rb:52:1:52:19 | call to reset |
|
||||
| associations.rb:52:1:52:33 | call to find |
|
||||
| associations.rb:53:1:53:7 | author1 |
|
||||
| associations.rb:53:1:53:13 | call to posts |
|
||||
| associations.rb:53:1:53:20 | call to reload |
|
||||
| associations.rb:53:1:53:34 | call to find |
|
||||
activeRecordSqlExecutionRanges
|
||||
| ActiveRecord.rb:9:33:9:67 | "name='#{...}' and pass='#{...}'" |
|
||||
| ActiveRecord.rb:19:16:19:24 | condition |
|
||||
@@ -53,6 +140,13 @@ activeRecordModelClassMethodCalls
|
||||
| ActiveRecord.rb:92:5:92:71 | call to update |
|
||||
| ActiveRecord.rb:98:13:98:54 | call to annotate |
|
||||
| ActiveRecord.rb:102:13:102:77 | call to annotate |
|
||||
| associations.rb:2:3:2:17 | call to has_many |
|
||||
| associations.rb:6:3:6:20 | call to belongs_to |
|
||||
| associations.rb:7:3:7:20 | call to has_many |
|
||||
| associations.rb:8:3:8:31 | call to has_and_belongs_to_many |
|
||||
| associations.rb:12:3:12:32 | call to has_and_belongs_to_many |
|
||||
| associations.rb:16:3:16:18 | call to belongs_to |
|
||||
| associations.rb:19:11:19:20 | call to new |
|
||||
potentiallyUnsafeSqlExecutingMethodCall
|
||||
| ActiveRecord.rb:9:5:9:68 | call to find |
|
||||
| ActiveRecord.rb:19:5:19:25 | call to destroy_by |
|
||||
@@ -68,10 +162,51 @@ potentiallyUnsafeSqlExecutingMethodCall
|
||||
activeRecordModelInstantiations
|
||||
| ActiveRecord.rb:9:5:9:68 | call to find | ActiveRecord.rb:5:1:15:3 | User |
|
||||
| ActiveRecord.rb:13:5:13:40 | call to find_by | ActiveRecord.rb:1:1:3:3 | UserGroup |
|
||||
| ActiveRecord.rb:13:5:13:46 | call to users | ActiveRecord.rb:5:1:15:3 | User |
|
||||
| ActiveRecord.rb:36:5:36:30 | call to find_by_name | ActiveRecord.rb:5:1:15:3 | User |
|
||||
| ActiveRecord.rb:56:7:56:40 | call to find_by | ActiveRecord.rb:5:1:15:3 | User |
|
||||
| ActiveRecord.rb:60:5:60:33 | call to find_by | ActiveRecord.rb:5:1:15:3 | User |
|
||||
| ActiveRecord.rb:62:5:62:34 | call to find | ActiveRecord.rb:5:1:15:3 | User |
|
||||
| associations.rb:19:11:19:20 | call to new | associations.rb:1:1:3:3 | Author |
|
||||
| associations.rb:21:9:21:21 | call to posts | associations.rb:5:1:9:3 | Post |
|
||||
| associations.rb:21:9:21:28 | call to create | associations.rb:5:1:9:3 | Post |
|
||||
| associations.rb:23:12:23:25 | call to comments | associations.rb:15:1:17:3 | Comment |
|
||||
| associations.rb:23:12:23:32 | call to create | associations.rb:15:1:17:3 | Comment |
|
||||
| associations.rb:25:11:25:22 | call to author | associations.rb:1:1:3:3 | Author |
|
||||
| associations.rb:27:9:27:21 | call to posts | associations.rb:5:1:9:3 | Post |
|
||||
| associations.rb:27:9:27:28 | call to create | associations.rb:5:1:9:3 | Post |
|
||||
| associations.rb:29:1:29:13 | call to posts | associations.rb:5:1:9:3 | Post |
|
||||
| associations.rb:29:1:29:22 | ... << ... | associations.rb:11:1:13:3 | Tag |
|
||||
| associations.rb:29:1:29:22 | ... << ... | associations.rb:15:1:17:3 | Comment |
|
||||
| associations.rb:31:1:31:12 | call to author= | associations.rb:1:1:3:3 | Author |
|
||||
| associations.rb:35:1:35:14 | call to comments | associations.rb:15:1:17:3 | Comment |
|
||||
| associations.rb:35:1:35:21 | call to create | associations.rb:15:1:17:3 | Comment |
|
||||
| associations.rb:37:1:37:13 | call to posts | associations.rb:5:1:9:3 | Post |
|
||||
| associations.rb:37:1:37:20 | call to reload | associations.rb:5:1:9:3 | Post |
|
||||
| associations.rb:42:1:42:13 | call to posts | associations.rb:5:1:9:3 | Post |
|
||||
| associations.rb:42:1:42:25 | call to push | associations.rb:5:1:9:3 | Post |
|
||||
| associations.rb:43:1:43:13 | call to posts | associations.rb:5:1:9:3 | Post |
|
||||
| associations.rb:43:1:43:27 | call to concat | associations.rb:5:1:9:3 | Post |
|
||||
| associations.rb:44:1:44:13 | call to posts | associations.rb:5:1:9:3 | Post |
|
||||
| associations.rb:44:1:44:19 | call to build | associations.rb:5:1:9:3 | Post |
|
||||
| associations.rb:45:1:45:13 | call to posts | associations.rb:5:1:9:3 | Post |
|
||||
| associations.rb:45:1:45:20 | call to create | associations.rb:5:1:9:3 | Post |
|
||||
| associations.rb:46:1:46:13 | call to posts | associations.rb:5:1:9:3 | Post |
|
||||
| associations.rb:46:1:46:21 | call to create! | associations.rb:5:1:9:3 | Post |
|
||||
| associations.rb:47:1:47:13 | call to posts | associations.rb:5:1:9:3 | Post |
|
||||
| associations.rb:47:1:47:20 | call to delete | associations.rb:5:1:9:3 | Post |
|
||||
| associations.rb:48:1:48:13 | call to posts | associations.rb:5:1:9:3 | Post |
|
||||
| associations.rb:48:1:48:24 | call to delete_all | associations.rb:5:1:9:3 | Post |
|
||||
| associations.rb:49:1:49:13 | call to posts | associations.rb:5:1:9:3 | Post |
|
||||
| associations.rb:49:1:49:21 | call to destroy | associations.rb:5:1:9:3 | Post |
|
||||
| associations.rb:50:1:50:13 | call to posts | associations.rb:5:1:9:3 | Post |
|
||||
| associations.rb:50:1:50:25 | call to destroy_all | associations.rb:5:1:9:3 | Post |
|
||||
| associations.rb:51:1:51:13 | call to posts | associations.rb:5:1:9:3 | Post |
|
||||
| associations.rb:51:1:51:22 | call to distinct | associations.rb:5:1:9:3 | Post |
|
||||
| associations.rb:52:1:52:13 | call to posts | associations.rb:5:1:9:3 | Post |
|
||||
| associations.rb:52:1:52:19 | call to reset | associations.rb:5:1:9:3 | Post |
|
||||
| associations.rb:53:1:53:13 | call to posts | associations.rb:5:1:9:3 | Post |
|
||||
| associations.rb:53:1:53:20 | call to reload | associations.rb:5:1:9:3 | Post |
|
||||
persistentWriteAccesses
|
||||
| ActiveRecord.rb:72:5:72:24 | call to create | ActiveRecord.rb:72:18:72:23 | call to params |
|
||||
| ActiveRecord.rb:76:5:76:66 | call to create | ActiveRecord.rb:76:24:76:36 | ...[...] |
|
||||
@@ -82,3 +217,4 @@ persistentWriteAccesses
|
||||
| ActiveRecord.rb:88:5:88:69 | call to update | ActiveRecord.rb:88:27:88:39 | ...[...] |
|
||||
| ActiveRecord.rb:88:5:88:69 | call to update | ActiveRecord.rb:88:52:88:68 | ...[...] |
|
||||
| ActiveRecord.rb:92:5:92:71 | call to update | ActiveRecord.rb:92:21:92:70 | call to [] |
|
||||
| associations.rb:31:16:31:22 | ... = ... | associations.rb:31:16:31:22 | author2 |
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
class Author < ActiveRecord::Base
|
||||
has_many :posts
|
||||
end
|
||||
|
||||
class Post < ActiveRecord::Base
|
||||
belongs_to :author
|
||||
has_many :comments
|
||||
has_and_belongs_to_many :tags
|
||||
end
|
||||
|
||||
class Tag < ActiveRecord::Base
|
||||
has_and_belongs_to_many :posts
|
||||
end
|
||||
|
||||
class Comment < ActiveRecord::Base
|
||||
belongs_to :post
|
||||
end
|
||||
|
||||
author1 = Author.new
|
||||
|
||||
post1 = author1.posts.create
|
||||
|
||||
comment1 = post1.comments.create
|
||||
|
||||
author2 = post1.author
|
||||
|
||||
post2 = author2.posts.create
|
||||
|
||||
author2.posts << post2
|
||||
|
||||
post1.author = author2
|
||||
|
||||
# The final method call in this chain should not be recognised as an
|
||||
# instantiation.
|
||||
post2.comments.create.create
|
||||
|
||||
author1.posts.reload.create
|
||||
|
||||
post1.build_tag
|
||||
post1.build_tag
|
||||
|
||||
author1.posts.push(post2)
|
||||
author1.posts.concat(post2)
|
||||
author1.posts.build
|
||||
author1.posts.create
|
||||
author1.posts.create!
|
||||
author1.posts.delete
|
||||
author1.posts.delete_all
|
||||
author1.posts.destroy
|
||||
author1.posts.destroy_all
|
||||
author1.posts.distinct.find(post_id)
|
||||
author1.posts.reset.find(post_id)
|
||||
author1.posts.reload.find(post_id)
|
||||
Reference in New Issue
Block a user