# Custom Rails(custom_rails)

Custom Rails is a Python web framework that aims to provide a similar experience to Ruby on Rails, but using the Python programming language. It is designed to help developers quickly build and deploy web applications with minimal configuration and boilerplate code. It uses a pattern similar to [Model-View-Controller (MVC)](https://en.wikipedia.org/wiki/Model-view-controller) pattern.


## Preparing the development environment

1. Install the dependencies: `pip install -r requirements.txt`
2. Install the library as editable (for development or until it is available on PyPI): `pip install -e .` - searches for setup.py, which is going to install the `custom_rails` package as editable

## Create an app with this framework

    python3 -m custom_rails new  
    
It will create the default template that contains the users' controller and the default views and styles.

## Run the development server:

    python3 -m custom_rails start  
    
Open your web browser and navigate to http://localhost:3000/.

## Views
In this web application framework, the views directory is where developers can add template files with a ".html.jinja2" extension. These files contain [Jinja2](https://jinja.palletsprojects.com/en/3.1.x/) components, which are used to dynamically generate HTML content for the web application. Jinja2 is a powerful template engine that offers features such as template inheritance, macros, and filters, and is designed to be user-friendly and flexible.

```
<!DOCTYPE html>
<html>
  <head>
    <title>{{ title }}</title>
  </head>
  <body>
    <h1>{{ heading }}</h1>
    <p>{{ content }}</p>
  </body>
</html>

```

## Request 

The Request class is used to extract data from a web request in Python and store it in a class representation. It has attributes such as method, path, query_string, and body. It also has a post attribute which holds data sent during a request, and a parse_q method to parse request bodies for POST requests as you can see below.
    
    req = Request(environ, start_response)
    username = req.post.get('username')

This class makes it easier to access request components and build web applications in Python.

## Response

This framework has different response classes that are used in a web application to generate and send different types of responses to the client based on the request received.

1. `Response`: This is the base class for all responses generated by the web application. It contains instance variables for headers, status code, content type, and response content. The make_response() method is used to send the response to the client.

2. `HttpResponse`: This class generates a pure HTTP response with a text content. It inherits from the Response class and takes a Request object, content, status code, and content type as parameters. The content is converted to bytes if it is a string.

3. `JinjaResponse`: This class generates an HTML response by rendering a Jinja template. It inherits from the Response class and takes a Request object, filename, parameters, status code, and content type as parameters. It reads the content of the file, decodes it into a string, and renders the template using the provided parameters.

4. `JsonResponse`: This class generates a pure JSON response with a text content. It inherits from the Response class and takes a Request object, content, status code, and content type as parameters. The content is converted to a JSON string before being added to the response content.

## Controllers

In this framework, creating a controller involves adding a Python file with a specific name in the "controllers" folder. For each function defined in that file, a corresponding path will be generated. For example, if you create a "companies.py" file with "index" and "show" functions defined, the paths "companies/index" and "companies/show" will be generated respectively. Every function defined in a controller file must return an instance of a response class described above.

```
def index(request):
    data = [
      {'name': 'Tol', 'profit': 10},
      {'name': 'NTT', 'profit': 100},
    ]
    return JsonResponse(request,data)

def show(request):
    data = [
      {'name': 'Tol', 'profit': 10},
    ]
    return JsonResponse(request,data)
```

You can see more examples [here](https://github.com/patcasrares/custom_rails/blob/develop/custom_rails/templates/controllers/users.py).

## Helpers

In the generated app, there is a directory called `helpers` where developers can add Python files containing helper functions. These functions can be used throughout the application to perform common tasks or to provide additional functionality. By organizing these functions in a separate directory, developers can keep the main codebase cleaner and easier to maintain. This directory also allows for the easy addition and removal of helper functions as needed, without affecting the main application code.

## Styles

In the generated app, there is a folder named `styles` that contains CSS files. By organizing the CSS files in a separate folder, developers can keep the codebase cleaner and easier to maintain. 

## Db Helpers 

The **Db** class provides several methods for executing SQL queries

1. **__init__(self, table_name)**: Initializes the class and establishes a connection to the database specified in config.ini. It also creates the table with the specified table_name if it doesn't already exist.
2. **_create_table_if_not_exists(self)**: Creates the table with the specified table_name if it doesn't already exist.
3. **execute_query(self, query, params=None)**: Executes a SQL query and commits the changes to the database.
4. **execute_select(self, query, params=None)**: Executes a SQL select query and returns the resulting rows.
5. **add(self, data)**: Adds a new row to the table with the data provided in the data dictionary.
6. **get_all(self)**: Returns all rows in the table as a list of dictionaries.
7. **get_by_id(self, id)**: Returns the row with the specified id as a dictionary.
8. **update(self, id, data)**: Updates the row with the specified id with the data provided in the data dictionary.
9. **delete(self, id)**: Deletes the row with the specified id.
10. **add_column(self, column_name, data_type)**: Adds a new column to the table with the specified column_name and data_type.
11. **delete_column(self, column_name)**: Deletes the column with the specified column_name from the table.

You can also make a custom class in the **db_models** folder like this one:

```
from custom_rails.db_static import *


class UserModel(DbStatic):
    pass
```

After that you'll be able to call UserModel.get_all() and so on. The class will automatically work with the **users table** from db.


## Contributing
We welcome contributions from the community! If you'd like to contribute to this project, please follow these guidelines:

1. Fork the repository and create your branch from develop.
2. Make your changes and ensure that they adhere to the coding style guidelines of the project.
3. Write tests for any new functionality or changes to existing functionality.
4. Ensure that all tests pass by running the appropriate command (e.g. pytest).
5. Create a pull request with a clear description of your changes and why they should be included.

