Chris Schmitz

Development & Design

Develop Faster With iTerm Profiles and Window Arrangements

My friend, Jon Kinney, was showing me how he’s able to jump into dev mode with his server, Rails console, database console, test suite and Vim all running at once using tmux and the tmuxinator gem. I was obviously jelly and I wanted to find a way to do something similar, but since I don’t really use terminal Vim for regular development I thought tmux was overkill for just starting up my server, consoles and tests so I decided to find a way to be more productive in iTerm 2.

Using split panes, profiles and window arrangments we can create a workspace that has everything we need to be productive and we can launch it with a single click. Here’s a preview of the kind of thing you can do:

Above we have a regular terminal session, Rails console, database console and server running in 4 different panes.

Getting Started With Laravel 4

With the release of Laravel 4 just around the corner a lot of people are trying to decide whether to start a new project with a stable 3.2 build or wait for the new version. I found myself in that position earlier this week so I decided to pull down Laravel 4 and work off that since I figured the transition would be smoother once a stable version 4 is released.

There isn’t really any documentation on how to get started with Laravel 4, and if you aren’t familiar with Composer, you might not know where to begin. That’s why I’m writing this. If you want to get started with Laravel 4 today, here’s how.

Extending ActiveModel via ActiveSupport::Concern

In a project I’ve been working on we have some functionality that is shared across multiple models - assigning a default record on a has_many association. Obviously we didn’t want to duplicate the methods in each model, so we explored our options for sharing the logic. We figure we could:

  1. Setup a HasDefault base class that we could extend our models from (i.e. class CreditCard < HasDefault)
  2. Extend the functionality of ActiveRecord::Base

Inspired by how has_secure_password is setup, we decided to extend the functionality of ActiveRecord::Base so we could just call a method inside of a model we wanted to expose the default record assignment methods and callbacks to. Here’s what we ended up with:

File: lib/active_model_extensions.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
module HasDefault
  extend ActiveSupport::Concern

  module ClassMethods
    def has_default
      attr_accessible :is_default

      before_create  :set_default_if_none_exists, unless: :is_default?
      before_save    :set_default,                if: :is_default?
      before_destroy :set_fallback_default,       if: :is_default?
    end
  end

  def set_default
    current_default = self.class.first conditions: { user_id: self.user_id, is_default: true }
    current_default.update_column(:is_default, false) if current_default
    self.update_column(:is_default, true) unless self.new_record?
  end

  def set_fallback_default
    if self.is_default?
      fallback = self.class.first conditions: { user_id: self.user_id, is_default: false }
      fallback.update_column(:is_default, true) if fallback
    end
  end

  def set_default_if_none_exists
    current_default = self.class.first conditions: { user_id: self.user_id, is_default: true }
    self.is_default = true unless current_default
  end
end

class ActiveRecord::Base
  include HasDefault
end

This allows us to call has_default in any model to expose the three callback methods for assigning the default record. Now all we need to do to in our model is

1
2
3
class CreditCard < ActiveRecord::Base
    has_default
end

and the default record assignment will be taken care of automatically. This assumes there is an is_default column in the table, but has_secure_password makes similar assumptions so that doesn’t really bother me.

The Breakdown

The big thing here is to make sure we are using ActiveSupport::Concern to extend the core Rails classes. This makes our lives a litter easier by helping us setup our class and instance methods. It takes everything inside the ClassMethods module and makes them available as, you guessed it, class methods. All the other methods in the HasDefault module get turned into instance methods.

From the code above, you can see we setup the has_default class methods

1
2
3
4
5
6
7
8
9
module ClassMethods
  def has_default
    attr_accessible :is_default

    before_create  :set_default_if_none_exists, unless: :is_default?
    before_save    :set_default,                if: :is_default?
    before_destroy :set_fallback_default,       if: :is_default?
  end
end

and then setup the methods used in the callbacks in the HasDefault module.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def set_default
  current_default = self.class.first conditions: { user_id: self.user_id, is_default: true }
  current_default.update_column(:is_default, false) if current_default
  self.update_column(:is_default, true) unless self.new_record?
end

def set_fallback_default
  if self.is_default?
    fallback = self.class.first conditions: { user_id: self.user_id, is_default: false }
    fallback.update_column(:is_default, true) if fallback
  end
end

def set_default_if_none_exists
  current_default = self.class.first conditions: { user_id: self.user_id, is_default: true }
  self.is_default = true unless current_default
end

Note: ActiveSupport::Concern used to support a module called InstanceMethods, but recently changed this to make anything outside of the ClassMethods module an instance method.

The last part of this example includes the HasDefault module in ActiveRecord::Base, which actually adds the module and exposes the has_default method to all classes extending ActiveRecord::Base, in other words, all of your models.

1
2
3
class ActiveRecord::Base
  include HasDefault
end

Why ActiveSupport?

It’s true, there are other ways to extend classes in Ruby, but using ActiveSupport::Concern makes things a little easier and definitely cleaner. It can also handle dependency resolution, which we didn’t need here, but is quite useful.

Further Reading

Code Highlighting and Marked Preview Styles in nvALT

Everyone loves nvALT, but the markdown preview you get out of the box isn’t anything special. Although there are some instructions on customizing the markdown preview in nvALT, he doesn’t go into specifics about where to find new styles or exactly how to import them.

Personally, I like the previews that I get in Marked, another one of Brett Terpstra’s creations. Knowing that the markdown preview styles are stored in a plain CSS file in nvALT, I figured it was the same for Marked.

The first place I looked was in ~/Library/Application Support/Marked, but I only found a Custom CSS folder that stored all of the custom stylesheets I had added. This makes sense since these are user settings. The core styles that aren’t editable by the user came with the application, which is why I checked the package contents of the application next, this is where I found the CSS files.

Note: You can view the package contents by going to the Marked.app file, right-clicking and selecting “Show Package Contents”.

I opened up github.css, copied the entire stylesheet and brought it over to my ~/Library/Application Support/Notational Velocity/custom.css file for nvALT. I restarted the program selected a different note (thanks Brett) and the new styles were live!

Further Customizations

If you look in the ~/Library/Application Support/Notational Velocity directory, you’ll notice that the template for the markdown preview is actually just an HTML file. As you can see, jQuery is being loaded from the Google CDN, so you should be able to import any external JS/CSS you’d like.

1
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.js"></script></code>

I haven’t played around with this much yet, but I did setup highlight.js to provide code highlighting. All I had to do was include a JS and CSS file from the download instructions and then call the init method. You can copy and paste the following code right above the `

Work Faster With Basic Shell Commands

Writing your own shell scripts may seem a little intimidating at first, but it’s actually quite simple and can drastically improve your workflow. Any sequence of commands you would run from the command line you can put into a shell script.

For example, when I want to start coding on a project, I usually end up opening my editor from the terminal and a new tab in my browser to view the project. This doesn’t take very long, but since it’s something I do multiple times every day, I figured I should try to automate it.

Let’s Scriptify It

I wanted to be able to pull up my editor and open the new browser tab right from the terminal so I came up with the following script to do just that:

1
2
3
4
# !/bin/sh

open -g 'http://localhost/chris-schmitz.com'
subl .

Breaking this down, here’s what each line does:

  1. # !/bin/sh - Invokes the shell, a standard starting line for a shell script
  2. open -g 'http://localhost/chris-schmitz.com' - Opens the URL for the project in a new tab of my default browser
  3. subl . - Opens the current directory in Sublime Text 2 (using TextMate the command would be mate .)

Now, assuming I saved the file as open.sh, I can run sh open.sh from the command line to open my editor and the browser tab. This was a great next step, but I thought I could do a little better.

A Shortcut for the Shortcut

I love TextExpander and any time I have text that I need to type on a regular basis I make a snippet for it. I figured this shell script is a perfect candidate for that.

I didn’t want to have to create a new file and then use a snippet to insert those three lines, so I use the following snippet to create the file with those three lines all in one step from the terminal.

1
echo "# \!/bin/sh\n\nopen -g 'http://localhost/%fill:site_name%'\nsubl ." >; open.sh

It’s just a one-liner with some escaped characters and line breaks that get inserted into the open.sh file. I set the shortcut for the snippet to qlp, short for Quick Launch Project.

Now I can create these shell scripts with ease, but I still didn’t like having to type sh open.sh all the time. I decided I wanted to make it shorter, so I aliased that to dev with the following command:

1
alias dev='sh open.sh'

Note: I’m using ZSH in my terminal, but I believe this command is the same for Bash.

My New Workflow

Now when I create a new project, all I have to do to get setup is:

  1. cd to the directory
  2. Expand my qlp snippet
  3. Run the dev command, and I’m ready to go

The main takeaway from this shouldn’t be the actual code, but the idea of how easy it is to automate repetitive tasks. A few basic shell commands can go a long way.

If you want to learn more about shell scripting, I highly recommend the Bash Guide for Beginners.

The MacBook Air and Web Development

I migrated to a MacBook Air as my primary development machine a few months ago and I think it is the ideal computer for a web developer. I think it’s the future of personal computing and that everyone should do themselves a favor and pick one up the first chance they get.

There are already some great articles reviewing the specs of the computer and why it is well-suited for web development, but I just wanted to point out a few, hopefully less-obvious, benefits of the machine for others who are considering making the switch.

Pros

There are a lot of things I like about the machine, but here are the big ones.

Focus

I actually enjoy the restrictions of the smaller screen. I no longer need to worry about where I’m going to position windows. They are either full screen or half screen, no more lost time organizing my windows.

As a result of the more minimalistic setup I’ve become accustomed to, I’ve found that I am less tolerant of distractions. Now, I turn off anything that can potentially distract me from the task at hand and I’ve been much more productive as a result.

Efficiency

At first I didn’t like them, but I use spaces all the time now. I can switch back and forth between personal and work space very efficiently.

Speed

I’ve never had a computer with an SSD before, and I completely underestimated how big of a difference this makes. It’s amazing. I’ve found my Air to be much more responsive than the much more powerful computers I was using previously. Like I said, I don’t want to get too technical here, so if you want more details on this stuff, I would check out Apple’s Performance page for more details.

Portability

I really didn’t think I would care too much about the portability of the machine, but now that I have been carrying it with me everywhere for the past few months I don’t think I could ever go back to a MacBook Pro. Even my wife’s 13” MBP feels clunky (and slow) relative to my Air.

One thing a lot of people don’t seem to realize is that the resolution of the Air is actually the same as a 15” MBP. This makes it feel a lot bigger than it is, and it has a Thunderbolt port if you want to hook it up to a larger display.

Cons

None… just kidding, but they are fairly obvious. You’re clearly getting a machine with less screen real estate and a slower processor, but you will not notice the difference unless you are doing a lot of graphics/video editing or 3D modeling.

Conclusion

Computers are typically reviewed on specs, and if you compare the MacBook Air to a more powerful machine, the MacBookAir might look limited, but if you compare it based on the user experience provided by the machine, nothing comes close.

Note: I am a complete Apple fanboy, but I was a Windows user for the majority of my life. I’ve even had a Windows 7 laptop that I worked on for about a year so I am able to make a somewhat fair comparison.

Other Relevant Articles

The CodeIgniter Decorator Library

I’ve been thinking a lot lately about how I can clean up my CodeIgniter code, specifically my controllers and views. CodeIgniter is dead simple to work with, but on larger, more complex projects I find that my controllers get a little long and my views get messy. Here are my main issues and how I’ve decided to address them.

Controllers

The main thing I don’t like doing in my controllers is all of the data retrieval and preparation to pass data to the view. Things like this:

1
2
$product = $this->product_model->find($product_id);
$company = $this->company_model->find($product->company_id);

And this:

1
2
3
4
5
$view_data = array(
    'product' => $product,
    'company' => $company
);
$this->load->view('products/view', '$view_data');

They just don’t feel right to have in the controller because they get in the way of the core function of a controller.

The controller receives user input and initiates a response by making calls on model objects. A controller accepts input from the user and instructs the model and a view port to perform actions based on that input.

Wikipedia

Views

I’ve always disliked putting any sort of logic in my views. In fact, anything beyond looping through an object or array and echoing its values makes me feel dirty. Things like setting a default value if an attribute is empty:

1
<?php echo empty($product->price) ? $product->price : 'N/A'; ?>

Or updating the layout of a page based on the type of user that is viewing it:

1
2
3
4
5
6
7
8
<?php if ($user->company_id == $product->company_id): ?>
    <div class="product-admin-links">
        <?php echo anchor('products/edit/' . $product->id, 'Edit Product Info'); ?>
        <?php echo anchor('products/inventory/' . $product->id, 'Edit Product Inventory'); ?>
    </div>
<?php else: ?>
    <?php echo anchor('cart/add/' . $product->id, 'Add to Cart'); ?>
<?php endif; ?>

Not only does this clutter up my views, but it also has to be repeated anywhere that I want to display information for a resource, in this case a product. I would much prefer to have this data prepared outside of the view and the controller, but it doesn’t really belong in the model either.

My Solution: Decorators

After seeing how a couple other frameworks handle this (FuelPHP and Rails) and thinking through what my ideal solution would be, I’ve started writing a decorator library and created a spark out of it.

The library allows you to put all of the data retrieval and preparation into a decorator, which is stored in application/decorators. A decorator must extend the CI_Deocrator class which gives it access to the CodeIgniter super global ($this) so you can load models and everything else like you are working in a controller or model. Going back to our earlier example, if you removed the data retrieval from the controller and preparation logic form your views, you would end up with something likes this:

File: application/controllers/products.php

1
2
3
4
public function view($product_id)
{
    $this->load->view('products/view', $this->decorator->decorate('product', 'view', $product_id));
}

As opposed to this:

1
2
3
4
5
6
7
8
9
10
11
12
13
public function view($product_id)
{
    $this->load->model('product_model', 'company_model');

    $product = $this->product_model->find($product_id);
    $company = $this->company_model->find($product->id);

    $view_data = array(
        'product' => $product,
        'company' => $company
    );
    $this->load->view('products/view', $view_data);
}

Tip: By default, the decorate() method will look for a class/method that matches the controller class/method, otherwise you can specify them as parameters (i.e. decorate('products', 'view')). It will also accept a third parameter as an array of parameters to pass to the decorator.

Going back at our views, we can now do this:

File: application/views/products/view.php

1
2
<?php echo $product->price; ?>
<?php echo $product_links; ?>

As opposed to this:

1
2
3
4
5
6
7
8
9
<?php echo $product->price ? $product->price : 'N/A'; ?>
<?php if ($user->company_id == $product->company_id): ?>
    <div class="product-admin-links">
        <?php echo anchor('products/edit/' . $product->id, 'Edit Product Info'); ?>
        <?php echo anchor('products/inventory/' . $product->id, 'Edit Product Inventory'); ?>
    </div>
<?php else: ?>
    <?php echo anchor('cart/add/' . $product->id, 'Add to Cart'); ?>
<?php endif; ?>

And our decorator looks like this:

File: application/decorators/products_decorator.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Product_decorator extends CI_Decorator {

    public function view($product_id)
    {
        $this->load->model('product_model', 'company_model');

        $this->view_data->company = $this->company_model->find($product->id);
        $product = $this->product_model->find($product_id);

        if (empty($product->price)) $product->price = 'N/A'

        $this->view_data->product = $product;

        if ($this->session->userdata('company_id') == $product->company_id)
        {
            $product_links = '<div class="product-admin-links">';
            $product_links .= anchor('products/edit/' . $product->id, 'Edit Product Info');
            $product_links .= anchor('products/inventory/' . $product->id, 'Edit Product Inventory');
            $product_links .= '</div>';
        }
        else
        {
            $product_links = anchor('cart/add/' . $product->id, 'Add to Cart');
        }

        $this->view_data->product_links = $product_links;
    }
}

Tip: The class variable, $view_data, that I’m referencing here is a variable that is automatically returned by the decorate() method. Using this, you don’t have to worry about explicitly returning the data, but if you do return something, decorate() will use that instead of the $view_data class variable.

Help Me Out

It’s definitely not perfect, and I certainly make no claims of it being bug-free, but it’s a start and I hope to keep improving it based on the feedback I receive (Hint, hint!). I’ve been using this on a couple of my larger projects the last month or so and it has really helped me clean up my controllers and views. Hopefully others find it useful too.

Cleaning Up Your CodeIgniter Controllers

One of the issues I’ve always had with CodeIgniter is that it’s kind of difficult to keep my controllers skinny and my models fat. Certain things that are typically handled in the model with other MVC frameworks are often handled in the controller in CI.

For example, it’s fairly common to see code like this in a controller:

application/controllers/products.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class Products extends CI_Controller {

    public function edit($product_id)
    {
        $this->form_validation
            ->set_rules('name', 'Name', 'required')
            ->set_rules('price', 'Price', 'required|decimal')
            ->set_rules('short_description', 'Short Description', 'required')
            ->set_rules('long_description', 'Long Description', 'required');

        if ($this->form_validation->run())
        {
            $attributes = array(
                'id' => $product_id,
                'name' => $this->input->post('name'),
                'price' => $this->input->post('price'),
                'short_description' => $this->input->post('short_description'),
                'long_description' => $this->input->post('long_description')
            );

            if ($this->company_model->update($attributes))
            {
                $this->messages->success('Product updated successfully!');
                redirect('products');
            }
            else
            {
                $this->messages->error('There was an error updating this product\'s attributes...');
            }
        }

        $product = $this->product_model->find($product_id);
        $company = $this->company_model->find($company_id);

        $view_data = array(
            'company' => $company,
            'product' => $company_settings
        );
        $this->load->view('products/edit', $view_data);
    }
}

and a model like this:

application/models/product_model.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Product_model extends CI_Model {

    public function find($product_id)
    {
        return $this->db
            ->where('id', $product_id)
            ->limit(1)
            ->get('products')
            ->row();
    }

    public function update($product_id, $attributes)
    {
        return $this->db
            ->where('id', $product_id)
            ->update('products', $attributes);
    }

    public function get_product_price_breaks($product_id)
    {
        return $this->db
            ->where('product_id', $product_id)
            ->get('product_price_breaks');
    }
}

Room for Improvement

At first glance this controller may not look too bad, and if you’re used to working with CodeIgniter you’ve probably seen something close to this pattern a lot. You may think that, “we are separating our code well and we aren’t running queries in our controller or anything so we’re good to go!”, but I think this is a common misconception in the CI community and that there are a few things we can do here to clean up our controllers and fatten up our models.

Move Validation Rules

In MVC frameworks that use an ORM you can setup validations for your data in the model, which helps keep controllers skinny. Since CodeIgniter doesn’t come with an ORM and the form validation class is all we have, it’s a good idea to make use of the ability to save validation rules to a config file.

application/config/form_validation.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$config = array(
    array(
        'field' => 'name',
        'label' => 'Name',
        'rules' => 'required'
    ),
    array(
        'field' => 'price',
        'label' => 'Price',
        'rules' => 'required|decimal'
    ),
    array(
        'field' => 'short_description',
        'label' => 'Short Description',
        'rules' => 'required'
    ),
    array(
        'field' => 'long_description',
        'label' => 'Long Description',
        'rules' => 'required'
    )
);

Although your config file can get a little long when you are working on a large application (not so much a problem if you are using Modular Extensions and can have module-level config files), it’s still better than fattening your controllers with validation rules all over.

Prep Form Data in the Model

Once we’ve validated our form in the controller and are ready to submit the data to the database, we may as well setup the associative array we need for the update inside the model.

1
2
3
4
5
6
7
8
9
10
11
12
13
14

public function update($product_id, $input)
{
    $attributes = array(
        'name' => $input['name'],
        'price' => $input['price'],
        'short_description' => $input['short_description'],
        'long_description' => $input['long_description']
    );

    return $this->db
        ->where('id', $product_id)
        ->update('products', $attributes);
}

Assign Secondary Queries to Object Attributes

One last optimization we can do is perform all of our queries for the view data and return them as a single object. This way we cut down on clutter in the controller and provide a more logical placeholder for the view data. In this case, we’ll make a product’s price breaks available via the price_breaks attribute ($product->price_breaks). We’ll also remove a couple lines of assigning the results from the model to the $view_data variable.

application/models/product_model.php

1
2
3
4
5
6
7
8

public function get_all_product_info($product_id)
{
    $product = $this->find($product_id);
    $product->price_breaks = $this->get_product_price_breaks($product_id);

    return $product;
}

application/controllers/products.php

1
2

$view_data['product'] = $this->product_model->get_all_product_info($product_id);

Summary

With just a few simple changes we’ve drastically simplified the controller and made it responsible for only what it needs to be. Take a look at our refactored code compared to our original code and look how much cleaner it is.

Refactored

application/controllers/products.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Products extends CI_Controller {

    public function edit($product_id)
    {
        if ($this->form_validation->run())
        {
            if ($this->product_model->update($product_id, $this->input->post()))
            {
                $this->messages->success('Product updated successfully!');
                redirect('products');
            }
            else
            {
                $this->messages->error('There was an error updating this product\'s attributes...');
            }
        }

        $view_data['product'] = $this->product_model->find($product_id);

        $this->load->view('products/edit', $view_data);
    }
}

application/models/product_model.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

class Product_model extends CI_Model {

    public function find($product_id)
    {
        return $this->db
            ->where('id', $product_id)
            ->limit(1)
            ->get('products')
            ->row();
    }

    public function get_all_product_info($product_id)
    {
        $product = $this->find($product_id);
        $product->price_breaks = $this->get_product_price_breaks($product_id);

        return $product;
    }

    public function update($product_id, $input)
    {
        $attributes = array(
            'name' => $input['name'],
            'price' => $input['price'],
            'short_description' => $input['short_description'],
            'long_description' => $input['long_description']
        );

        return $this->db
            ->where('id', $product_id)
            ->update('products', $attributes);
    }

    public function get_product_price_breaks($product_id)
    {
        return $this->db
            ->where('product_id', $product_id)
            ->get('product_price_breaks');
    }
}

application/config/form_validation.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

$config = array(
    array(
        'field' => 'name',
        'label' => 'Name',
        'rules' => 'required'
    ),
    array(
        'field' => 'price',
        'label' => 'Price',
        'rules' => 'required|decimal'
    ),
    array(
        'field' => 'short_description',
        'label' => 'Short Description',
        'rules' => 'required'
    ),
    array(
        'field' => 'long_description',
        'label' => 'Long Description',
        'rules' => 'required'
    )
);

Note: Another very significant benefit to putting more code in the models is that your code will be much easier to test. Models are easy to test because they almost always return something while controllers are difficult because they usually just display a view. Breaking your code up this way will make it a lot easier if you decide to provide test coverage for your application.

Initial Code

application/controllers/products.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

class Products extends CI_Controller {

    public function edit($product_id)
    {
        $this->form_validation
            ->set_rules('name', 'Name', 'required')
            ->set_rules('price', 'Price', 'required|decimal')
            ->set_rules('short_description', 'Short Description', 'required')
            ->set_rules('long_description', 'Long Description', 'required');

        if ($this->form_validation->run())
        {
            $attributes = array(
                'id' => $product_id,
                'name' => $this->input->post('name'),
                'price' => $this->input->post('price'),
                'short_description' => $this->input->post('short_description'),
                'long_description' => $this->input->post('long_description')
            );

            if ($this->company_model->update($attributes))
            {
                $this->messages->success('Product updated successfully!');
                redirect('products');
            }
            else
            {
                $this->messages->error('There was an error updating this product\'s attributes...');
            }
        }

        $product = $this->product_model->find($product_id);
        $company = $this->company_model->find($company_id);

        $view_data = array(
            'company' => $company,
            'product' => $company_settings
        );
        $this->load->view('products/edit', $view_data);
    }
}

application/models/product_model.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Product_model extends CI_Model {

    public function find($product_id)
    {
        return $this->db
            ->where('id', $product_id)
            ->limit(1)
            ->get('products')
            ->row();
    }

    public function update($product_id, $attributes)
    {
        return $this->db
            ->where('id', $product_id)
            ->update('products', $attributes);
    }

    public function get_product_price_breaks($product_id)
    {
        return $this->db
            ->where('product_id', $product_id)
            ->get('product_price_breaks');
    }
}

User Group-specific Controllers in CodeIgniter

I’ve been building out an application that has 2 types of users, corporate and operations. Each user type has access to a lot of the same methods, but operations has access to a little more than corporate. I’ve been struggling with how to break up the code for these two types of users since a lot of it is shared. At first, I thought I had a couple options:

1. Separate the logic into different directories

This would mean that I have completely separate controllers and I would be duplicating methods that are shared for each type of user. I started building this out as an HMVC application with a module for operations that provided the extra methods available to that type of user. This gave me regular URLs like products/edit/48 and operations users would get operations/products/edit/48.

Issues:

I found myself duplicating a lot of code for methods that were shared for each user type and I didn’t really want 2 URLs to access the same method.

2. Create user-specific methods in the same controller

I thought about prefixing methods with “operations” or “corporate” so I would have methods like “operations_edit”. Doing things this way I would have had to:

  1. See what type of user is trying to access the method
  2. See if a method with a prefix of their user type exists
  3. Route them accordingly

Issues:

I didn’t like this approach because my controllers would be huge and I’d have to manage 2 of the same method for each type of user, even though I’d try to share private methods with each user type to limit code duplication.

My Current Solution

I thought about it a little more and I decided the way I would like it to work is if I could have a single controller that both user types share, and then be able to provide a controller with user-specific methods if there is anything that I want to override or add only for one user type. I ended up with a file structure like this:

  • modules/products/controllers/products.php - The default
  • modules/products/controllers/operations.php - Operations only
  • modules/products/controllers/corporate.php - Corporate only

Then I use CodeIgniter’s _remap() method to route the user accordingly. This method will:

  1. Check if there is a controller that has methods specific to that user type (corporate.php or operations.php)
  2. Check to see if the requested method exists in that controller
  3. If the method exists, execute it, otherwise try to execute it from the default controller
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public function _remap($method, $arguments)
{
  $controller = strtolower($this->router->class);

  if ($controller === $this->uri->segment(1))
  {
      if ($this->acl->is_admin())
      {
          $user_type = 'operations';
      }
      else
      {
          $user_type = 'corporate';
      }

      if ($module = $this->load->module($controller .'/'. $user_type))
      {
          if ( ! method_exists($module, $method))
          {
              $module = $this->load->module($controller);
          }
      }
  }
  else
  {
      $module = $this->load->module($this->uri->segment(1) .'/'. $controller);
  }

  call_user_func_array(array($module, $method), $arguments);
}

Break It Down Now Ya’ll

We start out with CodeIgniter’s _remap() method and setup the $controller variable to be the requested class name lowercased.

1
2
3
4
public function _remap($method, $arguments)
{
  $controller = strtolower($this->router->class);
}

Then we add a check to see if the requested controller is the default controller for the module. I do this because I don’t want to always override the requested controller, I still want to be able to have controllers for things like product images and categories in the same module. If it’s not the default controller for the module, I just load the requested controller.

1
2
3
4
5
6
7
8
if ($controller === $this->uri->segment(1))
{

}
else
{
  $module = $this->load->module($this->uri->segment(1) .'/'. $controller);
}

If they are requesting the default controller, I check to see what type of user they are and assign to to a variable. This could, and probably should, be setup dynamically in most cases.

1
2
3
4
5
6
7
8
if ($this->acl->is_admin())
{
  $user_type = 'operations';
}
else
{
  $user_type = 'corporate';
}

Next, I attempt to load the module and their respective controller. If the controller exists, I check to see if the requested method exists. If the method doesn’t exist, that means that I haven’t created a group-specific method for that type of user so I load the default controller.

1
2
3
4
5
6
7
if ($module = $this->load->module($controller .'/'. $user_type))
{
  if ( ! method_exists($module, $method))
  {
      $module = $this->load->module($controller);
  }
}

And finally, we call the actual method.

1
call_user_func_array(array($module, $method), $arguments);