I would like to make sure that my code is properly organized / developed in accordance with the following paradigms / patterns:
- Model view controller
- Rails configuration console
- Skinny Controller, Fat Model
I have listed them here in the order that I consider the most important.
My problem
After reading several articles, in particular this one and this one too , I began to move some logic in my controllers into my models.
However, I can’t decide whether to move my search logic (explained in detail later) from the controller to the model, even after reading additional posts / articles like these: MVC Thinking and Model vs. controller , Separation problems and many others not listed here .
Scenario
View
Two pages:
Page 1 contains a text field and a submit button that sends user input as an argument in the parameters in the POST request to the second page.
The second page simply displays each neatObject in the given array, name it @coolList .
controller
- The method, calling it awesomeSearch , is called when the second page receives a POST request. (Thanks to Rails Routing)
- awesomeSearch should enter user input available as params [: searchString] , and work with the NeatObject model to create @coolList and make this list available for rendering the view.
Model
The NeatObject model processes requests from controllers and returns neatObjects back to these controllers.
The NeatObject model defines the relationship between neatObjects and other tables in our database.
Database
These are the attributes that make up each neatObject according to our database:
- id - int
- description - string
- address - string
- date_created - timestamp
What is missing?
How the controller works with the model to get matches for user input.
This is the part that I'm confused about. The logic itself is very simple, but I'm not sure which parts belong to the model and which parts belong to the controller.
If the controller passes the search string to the model and the model passes the results?
If the controller requests a model for all neatObjects , then save only those that match?
Is the solution a bit?
In order to be able to ask questions about specific bits of logic, I will tell you more about the search process.
Search depth
The process includes searching for neatObjects that matches the search string. It would be impossible to move on without determining what we think is appropriate for neatObjects . To make everything very simple, we will say that:
The neatObject element matches the search string if the search string is contained in its description or its address, ignoring case and leading / trailing spaces.
This definition is not guaranteed to be permanent. There are several things that can change our definition. We may need to test more attributes than just the address and description, perhaps if the guys in the database add a new important attribute or the guys in the user interface decide that users should be able to search by identifier. And, of course, the opposite of these scenarios means that we need to remove the attribute from the list of attributes that we are testing. There are many situations that can change our definition of conformity . We could even add or remove logic, perhaps if it is decided that we should only check the first word in the description attribute, or perhaps if we no longer ignore the case.
Now we know what determines compliance , and we know that our definition can change. Now we can more specifically define the search process.
Here is the steps diagram:
- Get a link to all neatObjects
- Scroll through each neatObject , placing each individual test
- Test passes - add / save neatObject in the list of results
- Test fails - do not save neatObject for results
- Make Results Viewable
I will refer to these steps while I show possible implementations.
Implementation
The search function can be easily implemented either in the NeatObject model or in the controller serving the view.
Normally, I would just write all the logic in the controller, but after I found out about the design of "Skinny controller, Fat model", I thought that this definitely applies in this situation. In this article, in particular, I thought about reorganizing my code after I saw that the author had implemented a search function in the model. The author function did not handle user input, so I was wondering how this should be handled.
Here's how I would write the code before learning about "SCFM":
Controller search logic:
#searches_controller.rb #This is the method invoked when second page receives POST request def search @neatObjects = NeatObjects.all.to_a @neatObjects.delete_if { |neatObject| !matches?(neatObject, params[:searchString]) } end def matches?(neatObject, searchString) if((neatObject.description.downcase.include? searchString.downcase) || (neatObject.address.downcase.include? searchString.downcase)) return true end return false end
This method gets a reference to all neatObjects (step 1) by calling .all () on the NeatObject model. It uses the delete_if array function to execute the match test on each neatObject and saves only those that pass (step 2). Step 3 is performed automatically automatically, since we store our results in an instance variable in the controller that serves the view.
The placement of logic in the controller is very straightforward, but when considering the "SCFM" design pattern, this seems very illogical.
I wrote another option in which the controller sends user input to a function in a model that will return the neatObjects element that matches .
Search logic in the model
#NeatObject.rb def self.get_matches_for(searchString) all.to_a.delete_if { |neighborhood| !matches?(searchString, neighborhood) } end def self.matches?(phrase, neighborhood) fields = [neighborhood.name, neighborhood.address] fields.map!(&:downcase) phrase.downcase! fields.each do |field| if ( (phrase.include? field) || (field.include? phrase) ) return true end end return false end
This method gets a complete list of neatObjects with all () (step 1). As in the first method, the model method uses delete_if to remove array elements ( neatObjects ) that do not meet certain criteria (passing the match test) (step 2). In this method, the controller serving the view calls the get_matches_for method on the NeatObject model and saves the results in the instance variable (step 3), for example: @neatObjects = NeatObject.get_matches_for( params[:searchString] )
I think the model model is cleaner and a little more convenient, but I will talk in more detail in the next section.
Concern
I see the pros and cons of both the model method and the controller method, but there are things that I still don't know about.
When I read this article, I referenced several times (just as I did here ), it was very logical that the model defined a function to return recently added people.
The controller did not have to implement logic to determine if a person was added. It makes sense that the controller should not, because it depends on the data itself. Perhaps a completely different implementation of the regency test for messages . Recent people may include people added this week, and recent messages are only those messages that were sent today.
The controller should just say People.find_recent or People.find_recent and know that it got the correct results.
Is it possible to say that the find_recent method find_recent also be modified to accept a time symbol and return objects for different time periods? Ex - People.find_in_time( :before_this_month ) or Messages.find_in_time( :last_year ) . Does this still follow the MVC and Rails pattern?
If the controller can find a match for user input using NeatObject.get_matches_for( searchString ) ?
I think the correspondence logic belongs to the model, because in testing certain / specific attributes are used, and these attributes differ depending on the data. We can have different attributes for different tables, and these attributes should definitely not be defined by the controller. I know that the controller depends on the model, and not vice versa, so the model must define these attributes, even if the rest of the logic is in the controller.
- And if the model defines the attributes that are used in the search, why not define the entire search function?
The text above explains why I think the model should handle the search logic and express most of my questions / concerns, however I have some opinions that favor another option.
- If the model concerns only data, how can one justify passing input to the user?
If the controller does not process the search logic, it still needs to send user input to the model. Does he clean it before shipping? (Delete leading / trailing space and reduce the line). Does it just send the exact input received from the user?
- Where is testing done to ensure that the input is not malicious?
Some of my biggest questions are:
- Does the answer to the question, where does the logic change for each case?
If the search / matching process was simpler, would the code position change? For example, if the search was easier:
If the only valid attribute was the address, and this is unlikely to change, would we just handle the search using the controller?
If we used an advanced search function, where the user decided which attributes should be included in the search, and also controlled some other factors, there would be a lot of user input to determine the arguments to the search function. Would it be too much logic or user input for placement in the model?
Finally
- How can I always be sure that I put the logic in the right place (MVC)?
- In my particular case, with this data and this search / matching process, how do I organize the code?
- What recommendations can be followed when looking for gray areas or are not sure where the logic is?