Practical Object-Oriented Design in Ruby: An Agile Primer (Addison-Wesley Professional Ruby)

Category: Programming
Author: Sandi Metz
4.7
All Hacker News 7
This Year Stack Overflow 2
This Month Stack Overflow 2

Comments

by anonymous   2019-07-21

I would wrap functionality in modules and classes, as there are some benefits to it:

a) you can easily write tests for code in classes (and for modules by including them in classes as mixins and testing the classes) b) you have control over the visibility of functions/methods and you can actually create an interface in case you have a consumer of the game that needs to access something more than just the game class c) it's easier to extend the functionality of parts of the game by creating new implementors of parts of the game's functionality

That said, there is no dogma on writing only in an Object Oriented way. In some cases (perhaps for scripts that will be used as command line scripts), just having some functions and code executing those functions might be enough (especially if the script is simple and short in general).

My advice regarding the structure of the little game you're building is to look for the interactions, the "verbs" that need to take place (ie the messages sent between objects) and then you'll come up with the classes that will send those messages (methods) and designing and structuring the game will get much easier I believe.

By the way, a good book that could help in the direction of designing software is the following:

http://www.amazon.com/Practical-Object-Oriented-Design-Ruby-Addison-Wesley/dp/0321721330

Hope the above help.

by sterlingw   2017-08-20
I just finished a book called "Practical Object Oriented Design in Ruby." It teaches you how to build OO applications that are pleasant to maintain. I highly recommend it.

http://www.amazon.com/Practical-Object-Oriented-Design-Ruby-...

by joeyrosztoczy1   2017-08-20
This is an all time favorite: Sandi Metz' Practical Object Oriented Design https://www.amazon.com/gp/product/0321721330/ref=as_li_tl?ie...

It takes the cognitive overhead off of the programming language completely and focus on design principles in a really modern learning style.

by ryanyogan   2017-08-20
If you want an amazing book on Design Patterns for ruby, http://www.amazon.com/Practical-Object-Oriented-Design-Ruby-...
by anonymous   2017-08-20

Models in Rails do not have to do database operation, they are just normal classes. Normally they are imbued with ActiveRecord magic when you subclass them from ActiveRecord::Base.

You can use a gem such as Virtus that will give you models with attributes. And for validations you can go with Vanguard. If you want something close to ActiveRecord but without the database and are running Rails 3+ you can also include ActiveModel into your model to get attributes and validations as well as have them working in forms. See Yehuda Katz's post for details on that.

In your case it will depend on the data you will consume. If all the datasources have the same basic format for example you could create your own base class to keep all the logic that you want to share across the individual classes (inheritance).

If you have a few different types of data coming in you could create modules to encapsulate behavior for the different types and include the models you need in the appropriate classes (composition).

Generally though you probably want to end up with one class per resource in the remote API that maps 1-to-1 with whatever domain logic you have. You can do this in many different ways, but following the method naming used by ActiveRecord might be a good idea, both since you learn ActiveRecord while building your class structure and it will help other Rails developers later if your API looks and works like ActiveRecords.

Think about it in terms of what you want to be able to do to an object (this is where TDD comes in). You want to be able to fetch a collection Model.all, a specific element Model.find(identifier), push a changed element to the remote service updated_model.save and so on.

What the actual logic on the inside of these methods will have to be will depend on the remote service. But you will probably want each model class to hold a url to it's resource endpoint and you will defiantly want to keep the logic in your models. So instead of:

response = api_client.get_user(123)

User user = User.new(response)

you will do

class User
  ...
  def find id
    @api_client.get_user(id)
  end
  ...
end

User.find(123)

or more probably

class ApiClient
  ...
  protected

  def self.uri resource_uri
    @uri = resource_uri
  end

  def get id
    # basically whatever code you envisioned for api_client.get_user
  end
  ...
end

class User < ApiClient
  uri 'http://path.to.remote/resource.json'
  ...
  def find id
    get(id)
  end
  ...
end

User.find(123)

Basic principles: Collect all the shared logic in a class (ApiClient). Subclass that on a per resource basis (User). Keep all the logic in your models, no other part of your system should have to know if it's a DB backed app or if you are using an external REST API. Best of all is if you can keep the integration logic completely in the base class. That way you have only one place to update if the external datasource changes.

As for going the other way, Rails have several good methods to convert objects to JSON. From the to_json method to using a gem such as RABL to have actual views for your JSON objects.

You can get validations by using part of the ActiveRecord modules. As of Rails 4 this is a module called ActiveModel, but you can do it in Rails 3 and there are several tutorials for it online, not least of all a RailsCast.

Performance will not be a problem except what you can incur when calling a remote service, if the network is slow you will be to. Some of that could probably be helped with caching (see another answer by me for details) but that is also dependent on the data you are using.

Hope that put you on the right track. And if you want a more thorough grounding in how to design these kind of structures you should pick up a book on the subject, for example Practical Object-Oriented Design in Ruby: An Agile Primer by Sandi Metz.

by anonymous   2017-08-20

Accessing the attribute through the getter has the advantage of providing encapsulation. The use of an instance variable to store the value is an implementation detail in some respects. Whether that's appropriate is, of course, situational. I don't recall reading anything explicit this style issue, however.

Found https://softwareengineering.stackexchange.com/questions/181567/should-the-methods-of-a-class-call-its-own-getters-and-setters, which discusses the issue from a language-independent point of view. Also found https://www.ruby-forum.com/topic/141107, which is ruby-specific, although it doesn't break any new ground, let alone imply a Ruby standard.

Update: Just came across the following statement on page 24 of http://www.amazon.com/Practical-Object-Oriented-Design-Ruby-Addison-Wesley/dp/0321721330/ref=sr_1_1?s=books&ie=UTF8&qid=1376760915&sr=1-1, a well-respected book on Ruby: "Hide the variables, even from the class that defines them, by wrapping them in methods." (emphasis added). It goes on to give examples of methods in the class using the accessor methods for access.

by anonymous   2017-08-20

What should be in a Module?

A module should contain code that is shared by different classes, or it can be added to a single class.

Do I even need a Module?

No, you can achieve the same thing with inheritance. If there is no code to share, then you probably don't need a module.

How do I make sure I can use these methods in my models and controllers where needed?

You can access them using the class from within a controller:

SalesForceAPI.get_data

"Module or class?" is an age-old question in Ruby, and any other language that supports both.

I would encapsulate the functionality for each API in a class. For example, you can have a SalesForceAPI class. Then, if you need to share functionality between the classes for authenticating, creating a folder, auditing files, or downloading files, you can create an API class.

Each of the classes that needs access to the API class would inherit from it:

SalesForceAPI < API

You can achieve the same thing by creating an API module, and mixing it into the other classes. In this case, it's largely a matter of preference, because either solution is good.

FWIW, I don't know if there would be much shared functionality for authenticating, creating a folder, auditing files, or downloading files, because each API can work quite differently. However, if they are REST API's then you may create some REST helper methods and put them in the API class.

A great, easy to understand, and complete resource on the topic is Practical Object Oriented Design Ruby.

by anonymous   2017-08-20

All this is only my personal opinion on this broad and open topic. Some people might disagree and funny enough they can.

As of general guidelines you should use Cucumber to test entire application stack, something that is called user experience. This application stack might be composed of many smaller independent objects but, same as user using your application wouldn't be interested in those details, your cucumber test shouldn't care for them and focus instead on outer layer of your application.

RSpec( in your setup!) should on the other hand put a main focus on those small objects, building blocks of your application.

There is a big problem with small application examples from the books: They are to small!

Boarder between outer layer of your application and its interiors is to blurred. Entire application is build with two objects! It is hard to distinguished what should test what. As your application grow in size its getting more obvious what is a user experience test(cucumber) and what is object - state test/message expectation test(RSpec).

Using your second example:

With this Cucumber story:

Given I am at the login page
When I fill in the right username and password
Then I should be at the index page

Rspec:

You will probably have some sort of User model:

  • test user name syntax(must start with capital for example)
  • test password must be 7 characters have numbers etc...

You can have some sort of authentication object:

  • test for valid and invalid logins etc
  • test for exceptions being thrown ....

What if your Authentication database is on different server?

  • mock connection and authentication database and test if database receive an request from authentication object.

And yada yada yada... Forever your cucumber test will guard general purpose of your application user login. Even when you add or change behaviour, under the hood, as long as this test pass you can be confident that user can login.

  • you should test things once(in one place)
  • object its responsible for testing its incoming massage(object public interface) for state(return value)
  • when your object depends on other object(sends its a message) don't test it for state(return value) that object is responsible for it.
  • when your object depends on other object(sends its a message) mock that second object and test if it receive your message.

Generally your question is too broad and you wont find a single answer, different people will have different ideas. What you should focus on is to test, as good as you can and with time you will definitely find right way for you. Not a great test suit is much better then none.

Because good design helps to write good test I would recommend Practical Object-Oriented Design in Ruby: An Agile Primer by Sandi Metz(which is one of a best book I've read)

by anonymous   2017-08-20

In Practical Object-Oriented Design in Ruby, what Sandi Metz recommend you do to overcome calling super is to define a "hook" method in the super-class, which only job would be to get over-written by the child methods. In your case, you could do something like this:

class SuperClass
  def self.do_something
    puts "getting ready..."
    do_something_child
    puts "done."
  end

  def self.do_something_child; end
end

class SubClass < SuperClass
  def self.do_something_child
    puts "doing something ..."
  end
end

SubClass.do_something