Skip to content

Feature/Discussion: Scopes #306

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 34 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
75e4a1e
Added Query class for chain sql queries
CarlosBonetti Jan 28, 2013
7b032ab
Added support to custom scopes
CarlosBonetti Jan 29, 2013
ef95e1e
Added support to multiple wheres in a query
CarlosBonetti Jan 29, 2013
e6e6fc4
initial scope structure, going to merge with the query builder
anther Feb 6, 2013
4589c66
added calls to scopes within Model
anther Feb 6, 2013
f07dd35
added ability to call named scopes statically
anther Feb 6, 2013
82afd93
Bug fixes for finders
anther Feb 6, 2013
76973e6
Added documentation all around the usage of named parameters, Fixed b…
anther Feb 6, 2013
3756dac
Updated scopes to use static variables as opposed to function calls, …
anther Feb 10, 2013
2ea8a54
Fixed bugs for where the static scopes were not declared
anther Feb 10, 2013
70a969e
Added test to see if scopes worked through relations
anther Feb 10, 2013
b845ea3
Fixed bug with default scopes and for the base case of a ::scoped() b…
anther Mar 3, 2013
f70f885
Updated scopes to be able to call scopes on an object instance
anther Mar 4, 2013
9ba4680
Fixed bug where Query.php did not generate a an "IS NULL" query corre…
anther Mar 5, 2013
9f0f4a0
Bug fix to make sure NULL is not included as an anonymous parameter
anther Mar 5, 2013
474deeb
Made count() work within a scope context
anther Mar 5, 2013
3a44557
Added failing test to scopes
anther Mar 8, 2013
c162c2c
Made sure scope always returns an instance of Scope
anther Mar 9, 2013
fdbc957
Added exists function to scopes
anther Mar 9, 2013
960fa7c
Bug fixes, completely separated scope of OptionBinder and Scope
anther Apr 24, 2013
c84af0a
Changes to make all previous tests pass
anther Apr 24, 2013
8ef0ded
Set ActiveRecord.php back to its base state
anther Apr 26, 2013
fa1d4b9
Removed frivilous include
anther Apr 26, 2013
abb66b3
Fix for models that have no default scope
anther Apr 26, 2013
310c695
Fixed misplaced comment and removed no longer used method
anther Apr 26, 2013
ee057b8
Added ability for a scope to be used as an array
anther Jul 11, 2013
26ae232
Added functionality for generically appending scope calls similar to …
anther Jul 11, 2013
421e2d4
Added test for appending joins to eachother
anther Jul 11, 2013
cf26638
Fixed bug where WHERE conditions were duplicated
anther Jul 11, 2013
e804f9b
added extra comment
anther Jul 11, 2013
d884c6a
fixed bug that would result in foreach not working on a scope
anther Jul 17, 2013
1c3e1eb
Added countable interface to scope
anther Jul 17, 2013
242c912
Added arrayaccess to scopes
anther Jul 19, 2013
041d765
Merge branch 'base' into scopes
anther Jul 24, 2013
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 168 additions & 11 deletions lib/Model.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,14 @@ class Model
* @var Errors
*/
public $errors;

/**
* Contains model values as column_name => value
*
* @var array
*/
private $attributes = array();

/**
* Flag whether or not this model's attributes have been modified since it will either be null or an array of column_names that have been modified
*
Expand Down Expand Up @@ -1271,10 +1271,10 @@ public function reset_dirty()
*
* @var array
*/
static $VALID_OPTIONS = array('conditions', 'limit', 'offset', 'order', 'select', 'joins', 'include', 'readonly', 'group', 'from', 'having');
static $VALID_OPTIONS = array('conditions', 'limit', 'offset', 'order', 'select', 'joins', 'include', 'readonly', 'group', 'from', 'having', 'scope_options');

/**
* Enables the use of dynamic finders.
* Enables the use of dynamic finders and scopes.
*
* Dynamic finders are just an easy way to do queries quickly without having to
* specify an options array with conditions in it.
Expand Down Expand Up @@ -1349,9 +1349,120 @@ public static function __callStatic($method, $args)
$options['conditions'] = SQLBuilder::create_conditions_from_underscored_string(static::connection(),substr($method,9),$args,static::$alias_attribute);
return static::count($options);
}

if($scope = static::check_for_named_scope($method))
{
return static::scoped()->$method();
}
throw new ActiveRecordException("Call to undefined method: $method");
}

/**
* Puts the model into "Scoped" mode. It will allow the appending of options
* arrays.
*
* Usage
* <code>
* Model::scoped()->where('name="tito"');
* Model::scoped()->where('name="tito"')->limit(3)->all();
* </code>
* It allows parameterized named scopes to be declared.
*
* For Example:
* <code>
* public function last_few($number)
* {
* return self::scoped()->limit($number)->order('created_at DESC');
* }
* //Used as Model::last_few(5)->all(); Will return the latest 5 created records
* </code>
*
* @return Scopes
*/
public static function scoped()
{
require_once(__DIR__.'/Scope.php');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using an include/require statement here seems dirty. Could we move it?

$instance = new static();
return $instance->scope();
}

public function scope()
{
return new Scopes($this);
}

/**
* To be overridden by the model. It should return an array of options that are named
* by their key.
*
* <code>
* public function named_scopes()
* {
* public static $named_scopes = array(
* 'is_tito'=>array(
* 'conditions'=>'name="tito"',
* ),
* 'last_two'=>array(
* 'order'=>'created_at DESC',
* 'limit'=>2,
* ),
* );
* Model::is_tito()->find('all');
* </code>
*
* @return array|null An array of the options within the named scope
*/
public static function check_for_named_scope($scope)
{
if(!isset(static::$named_scopes))
{
return null;
}
$scopes = static::$named_scopes;
if(array_key_exists($scope,$scopes))
return $scopes[$scope];
else
return null;
}

/**
* Returns true if the model has a default scope declared
*
* @return
*/
public function get_default_scope()
{
$scope = $this->default_scope();
if($scope)
{
return $scope;
}
if(isset(static::$default_scope))
return static::$default_scope;
return null;
}

/**
* To be overridden by the model. It should return an options array that is
* to be applied every time the model is called
*
* <code>
* public function default_scope()
* {
* return array(
* 'conditions'=>'deleted == 0',
* 'limit'=>3,
* );
* }
* //Model::all() will then never return a result that is flagged as deleted
* //unless you call Model::scoped()->disable_default_scope()->all();
* </code>
*
* @return array An array of finder options
*/
public function default_scope()
{
return array();
}

/**
* Enables the use of build|create for associations.
Expand Down Expand Up @@ -1411,7 +1522,6 @@ public static function count(/* ... */)
$args = func_get_args();
$options = static::extract_and_validate_options($args);
$options['select'] = 'COUNT(*)';

if (!empty($args) && !is_null($args[0]) && !empty($args[0]))
{
if (is_hash($args[0]))
Expand Down Expand Up @@ -1522,7 +1632,7 @@ public static function last(/* ... */)
public static function find(/* $type, $options */)
{
$class = get_called_class();

if (func_num_args() <= 0)
throw new RecordNotFound("Couldn't find $class without an ID");

Expand Down Expand Up @@ -1559,10 +1669,12 @@ public static function find(/* $type, $options */)
//find by pk
elseif (1 === count($args) && 1 == $num_args)
$args = $args[0];

// anything left in $args is a find by pk
if ($num_args > 0 && !isset($options['conditions']))
if ($num_args > 0 && (!isset($options['conditions']) ||
(isset($options['scope_options']) && !$options['scope_options']->added_unscoped_conditions)))
{
return static::find_by_pk($args, $options);
}

$options['mapped_names'] = static::$alias_attribute;
$list = static::table()->find($options);
Expand Down Expand Up @@ -1682,11 +1794,9 @@ public static function pk_conditions($args)
public static function extract_and_validate_options(array &$array)
{
$options = array();

if ($array)
{
$last = &$array[count($array)-1];

try
{
if (self::is_options_hash($last))
Expand All @@ -1703,6 +1813,53 @@ public static function extract_and_validate_options(array &$array)
$options = array('conditions' => $last);
}
}
$options = self::append_default_scope_to_options($options);
return $options;
}

/**
*
* Applies the Model's default scope if it has not been disabled through
* a call to Model::disable_default_scope()
*
* @param array &$array An array
* @return array A valid options array
*/
public static function append_default_scope_to_options($options)
{
if(isset($options['scope_options']))
{
$scope = $options['scope_options'];
}
else
{
$scope = static::scoped();
}

if(!isset($options['scope_options']) && !$scope->default_scope())//Model does not use scopes at all
{
return $options;
}

if($options)
{
$scope->added_unscoped_conditions = true;
$scope = $scope->add_scope($options);
}

//Default scope is always applied last
if($scope->default_scope_is_enabled() && $default = $scope->default_scope())
{
$options = $scope->add_scope($default)->get_options();
}
elseif($options)
{
$options = $scope->get_options();
}
if($scope->remove_scope_from_hash_after_adding_default_scope)
{
unset($options['scope_options']);
}
return $options;
}

Expand Down
Loading