<commit_before>#coding: utf8

# The MIT License (MIT)
#
# Copyright (c) 2013 PengYi
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal in
# the Software without restriction, including without limitation the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
# the Software, and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
from __future__ import with_statement
from contextlib import contextmanager
from pyrails.activerecord.collection import Collection, HasManyCollection, HasAndBelongsToManyCollection
from pyrails.activerecord.method_missing import FindMethodMissing, CreateOrBuildMethodMissing
from pyrails.activesupport import classproperty, Inflection, ValidationError
from pyrails.activerecord.associations import Association
from pyrails.activerecord.validation import Validates
import pyrails

# from pyrails.activesupport import RecordValidateError, RecordHasNotBeenPersisted


class ActiveRecord(object):
    """
    A ORM. You can use it like this:
    - Query with chain 
        - where:
          User.where(name='pengyi').where('age != ?', 28).where('birthday between ? and ?', '1982-01-01', '1986-01-01')
         # => SELECT FROM users WHERE name = 'pengyi' AND age != 28 AND birthday between '1982-01-01' and '1986-01-01'
        
        - limit 
          User.where(name='pengyi').limit(10, 1) 
         # => SELECT FROM users WHERE name = 'pengyi' LIMIT 10 OFFSET 1
         
        - group/order/having ....
          User.where(name='pengyi').order('name DESC').group('name').having(age = 10)
         # => SELECT users.* FROM users WHERE users.name = 'pengyi' GROUP BY users.name HAVING users.age = 10 ORDER BY name DESC LIMIT 10 OFFSET 1
    
    - Query with method missing
      User.find_by_name('pengyi')
     # => SELECT FROM users WHERE name = 'pengyi'

      User.find_by_name_and_age('pengyi', 28)
     # => SELECT FROM users WHERE name = 'pengyi' AND age = 28
     
    - Create a record in database
      User.create(name = 'pengyi', age = 28)
     # => INSERT INTO users (name, age) VALUES ('pengyi', 28)
      
      User(name = 'pengyi', age = 28).save()
     # => INSERT INTO users (name, age) VALUES ('pengyi', 28)
     
    - Update
      u = User.find(1)  # 1 meams id
      u.update_attributes(name = 'poy', age = 30)
     # => UPDATE users SET name = 'poy', age = 30 WHERE id = 1
    
      User.where(age=20).update_all(name='poy', age='40)
     # => UPDATE users SET name = 'poy', age = 40 WHERE age = 20
    
    - Delete
      u = User.find(1)
      u.delete()
     # => DELETE FROM users WHERE id = 1
     
      u = User.where(age=20).delete_all()
     # => DELETE FROM users WHERE age = 20
    """
    __db__ = None

    class __metaclass__(type):
        def __init__(cls, name, bases, attr):
            model_instance = type.__init__(cls, name, bases, attr)
            if name != 'ActiveRecord':
                for assocation in Association.next:
                    assocation.register(cls)
                # set the validate function
                for on in ('save', 'create', 'update'):
                    for validate_partial in Validates.next(on):
                        cls.validate_func_dict.setdefault(on, []).append(validate_partial)
            return model_instance
        
        def __getattribute__(self, name):
            try:
                return type.__getattribute__(self, name)
            except AttributeError, _:
                if FindMethodMissing.match(name):
                    return FindMethodMissing(self, name)
                raise

    def __init__(self, **attributes):
        self.errors = {}
        for k, v in attributes.iteritems():
            setattr(self, k, v)

    def __setattr__(self, name, value):
        relt = super(ActiveRecord, self).__setattr__(name, value)
        # check a assoication, set association.target.id
        # eg. Card belong_to User
        #      u = User.find(1)
        #      card.user = u
        # so:  card.user_id = u.id 
        association = self.association_of(name)
        if association:
            if association.is_belongs_to():
                setattr(self, association.foreign_key, value.id)
            elif association.is_has_and_belongs_to_many():
                setattr(self, association.association_foreign_key, value.id)
            # elif association.is_has_one():
            #     setattr(value, association.foreign_key, self.id)
        return relt

    def __getattribute__(self, name):
        try:
            return super(ActiveRecord, self).__getattribute__(name)
        except AttributeError, _:
            if name in self.column_name_set:
                return None
            association = self.association_of(name)
            if association:
                if association.is_belongs_to():
                    # A belongs_to B
                    # A.B  => "SEELCT B.* FROM B WHERE id = A.B_id"
                    return association.target.find(getattr(self, association.foreign_key))
                elif association.is_has_one():
                    # A has_one B
                    # A.B => "SELECT B.* FROM B WHERE A_id = A.id"
                    return association.target.where(**{association.foreign_key: self.id}).first
                elif association.is_has_many():
                    # A has_many B through C
                    if association.through:
                        # A.bs  => "SELECT B.* FROM B INNER JOIN C ON C.b_id = B.id AND C.a_id = A.id"
                        through_association = self.association_of(association.through)
                        if through_association.is_has_many():
                            # fk_value={}, has_and_belongs_to_many_association=None):
                            return HasManyCollection(association.target, fk_value={through_association.foreign_key: self.id}) \
                                        .joins(association.through) \
                                        .where('%s.%s = %s' % (association.through, through_association.foreign_key, self.id))
                    else:
                        # A has_many B
                        # A.bs => "SELECT B.* FROM B WHERE A_id = A.id"
                        return HasManyCollection(association.target, fk_value={association.foreign_key: self.id}).where(**{association.foreign_key: self.id})
                else: # has_and_belongs_to_many
                    join_str = 'INNER JOIN %s ON %s.%s = %s.id' % (association.join_table, association.join_table, association.association_foreign_key, association.target.table_name)
                    # return association.target.joins(join_str).where('%s.%s = %s' % (association.join_table, association.foreign_key, self.id)
                    return HasAndBelongsToManyCollection(association.target, fk_value={association.foreign_key: self.id}) \
                                        .joins(join_str).where('%s.%s = %s' % (association.join_table, association.foreign_key, self.id))
            if CreateOrBuildMethodMissing.match(name):
                return CreateOrBuildMethodMissing(self, name)
            raise

    @classproperty
    def table_name(cls):
        if not hasattr(cls, '__table_name__'):
            table_name = Inflection.pluralize_of(cls.__name__)
            cls.__table_name__ = Inflection.hungarian_of(table_name)
        return cls.__table_name__

    @classproperty
    def table_name_sql(cls):
        if not hasattr(cls, '__table_name_sql__'):
            cls.__table_name_sql__ = '`%s`' % cls.table_name
        return cls.__table_name_sql__

    @classproperty
    def column_names(cls):
        if not hasattr(cls, '__column_names__'):
            cls.__column_names__ = [ x.name for x in cls._get_db().get_table_by(cls.table_name).columns ]
        return cls.__column_names__

    @classproperty
    def column_name_set(cls):
        if not hasattr(cls, '__column_name_set__'):
            cls.__column_name_set__ = set(cls.column_names)
        return cls.__column_name_set__

    @classmethod
    def column_names_sql(cls, table_alias=None):
        if not hasattr(cls, '__column_names_sql__'):
            if table_alias:
                cls.__column_names_sql__ = ', '.join([ '%s.`%s` AS %s_r%s' % (table_alias, cn, table_alias, idx) for idx, cn in enumerate(cls.column_names) ])
            else:
                cls.__column_names_sql__ = ', '.join( map(lambda x: '`%s`' % x, cls.column_names) )<commit_msg>add ActiveRecord.singularize_name classproperty<commit_after>#coding: utf8

# The MIT License (MIT)
#
# Copyright (c) 2013 PengYi
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal in
# the Software without restriction, including without limitation the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
# the Software, and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
from __future__ import with_statement
from contextlib import contextmanager
from pyrails.activerecord.collection import Collection, HasManyCollection, HasAndBelongsToManyCollection
from pyrails.activerecord.method_missing import FindMethodMissing, CreateOrBuildMethodMissing
from pyrails.activesupport import classproperty, Inflection, ValidationError
from pyrails.activerecord.associations import Association
from pyrails.activerecord.validation import Validates
import pyrails

# from pyrails.activesupport import RecordValidateError, RecordHasNotBeenPersisted


class ActiveRecord(object):
    """
    A ORM. You can use it like this:
    - Query with chain 
        - where:
          User.where(name='pengyi').where('age != ?', 28).where('birthday between ? and ?', '1982-01-01', '1986-01-01')
         # => SELECT FROM users WHERE name = 'pengyi' AND age != 28 AND birthday between '1982-01-01' and '1986-01-01'
        
        - limit 
          User.where(name='pengyi').limit(10, 1) 
         # => SELECT FROM users WHERE name = 'pengyi' LIMIT 10 OFFSET 1
         
        - group/order/having ....
          User.where(name='pengyi').order('name DESC').group('name').having(age = 10)
         # => SELECT users.* FROM users WHERE users.name = 'pengyi' GROUP BY users.name HAVING users.age = 10 ORDER BY name DESC LIMIT 10 OFFSET 1
    
    - Query with method missing
      User.find_by_name('pengyi')
     # => SELECT FROM users WHERE name = 'pengyi'

      User.find_by_name_and_age('pengyi', 28)
     # => SELECT FROM users WHERE name = 'pengyi' AND age = 28
     
    - Create a record in database
      User.create(name = 'pengyi', age = 28)
     # => INSERT INTO users (name, age) VALUES ('pengyi', 28)
      
      User(name = 'pengyi', age = 28).save()
     # => INSERT INTO users (name, age) VALUES ('pengyi', 28)
     
    - Update
      u = User.find(1)  # 1 meams id
      u.update_attributes(name = 'poy', age = 30)
     # => UPDATE users SET name = 'poy', age = 30 WHERE id = 1
    
      User.where(age=20).update_all(name='poy', age='40)
     # => UPDATE users SET name = 'poy', age = 40 WHERE age = 20
    
    - Delete
      u = User.find(1)
      u.delete()
     # => DELETE FROM users WHERE id = 1
     
      u = User.where(age=20).delete_all()
     # => DELETE FROM users WHERE age = 20
    """
    __db__ = None

    class __metaclass__(type):
        def __init__(cls, name, bases, attr):
            model_instance = type.__init__(cls, name, bases, attr)
            if name != 'ActiveRecord':
                for assocation in Association.next:
                    assocation.register(cls)
                # set the validate function
                for on in ('save', 'create', 'update'):
                    for validate_partial in Validates.next(on):
                        cls.validate_func_dict.setdefault(on, []).append(validate_partial)
            return model_instance
        
        def __getattribute__(self, name):
            try:
                return type.__getattribute__(self, name)
            except AttributeError, _:
                if FindMethodMissing.match(name):
                    return FindMethodMissing(self, name)
                raise

    def __init__(self, **attributes):
        self.errors = {}
        for k, v in attributes.iteritems():
            setattr(self, k, v)

    def __setattr__(self, name, value):
        relt = super(ActiveRecord, self).__setattr__(name, value)
        # check a assoication, set association.target.id
        # eg. Card belong_to User
        #      u = User.find(1)
        #      card.user = u
        # so:  card.user_id = u.id 
        association = self.association_of(name)
        if association:
            if association.is_belongs_to():
                setattr(self, association.foreign_key, value.id)
            elif association.is_has_and_belongs_to_many():
                setattr(self, association.association_foreign_key, value.id)
            # elif association.is_has_one():
            #     setattr(value, association.foreign_key, self.id)
        return relt

    def __getattribute__(self, name):
        try:
            return super(ActiveRecord, self).__getattribute__(name)
        except AttributeError, _:
            if name in self.column_name_set:
                return None
            association = self.association_of(name)
            if association:
                if association.is_belongs_to():
                    # A belongs_to B
                    # A.B  => "SEELCT B.* FROM B WHERE id = A.B_id"
                    return association.target.find(getattr(self, association.foreign_key))
                elif association.is_has_one():
                    # A has_one B
                    # A.B => "SELECT B.* FROM B WHERE A_id = A.id"
                    return association.target.where(**{association.foreign_key: self.id}).first
                elif association.is_has_many():
                    # A has_many B through C
                    if association.through:
                        # A.bs  => "SELECT B.* FROM B INNER JOIN C ON C.b_id = B.id AND C.a_id = A.id"
                        through_association = self.association_of(association.through)
                        if through_association.is_has_many():
                            # fk_value={}, has_and_belongs_to_many_association=None):
                            return HasManyCollection(association.target, fk_value={through_association.foreign_key: self.id}) \
                                        .joins(association.through) \
                                        .where('%s.%s = %s' % (association.through, through_association.foreign_key, self.id))
                    else:
                        # A has_many B
                        # A.bs => "SELECT B.* FROM B WHERE A_id = A.id"
                        return HasManyCollection(association.target, fk_value={association.foreign_key: self.id}).where(**{association.foreign_key: self.id})
                else: # has_and_belongs_to_many
                    join_str = 'INNER JOIN %s ON %s.%s = %s.id' % (association.join_table, association.join_table, association.association_foreign_key, association.target.table_name)
                    # return association.target.joins(join_str).where('%s.%s = %s' % (association.join_table, association.foreign_key, self.id)
                    return HasAndBelongsToManyCollection(association.target, fk_value={association.foreign_key: self.id}) \
                                        .joins(join_str).where('%s.%s = %s' % (association.join_table, association.foreign_key, self.id))
            if CreateOrBuildMethodMissing.match(name):
                return CreateOrBuildMethodMissing(self, name)
            raise

    @classproperty
    def singularize_name(cls):
        return Inflection.hungarian_of(Inflection.singularize_of(cls.__name__))

    @classproperty
    def table_name(cls):
        if not hasattr(cls, '__table_name__'):
            table_name = Inflection.pluralize_of(cls.__name__)
            cls.__table_name__ = Inflection.hungarian_of(table_name)
        return cls.__table_name__

    @classproperty
    def table_name_sql(cls):
        if not hasattr(cls, '__table_name_sql__'):
            cls.__table_name_sql__ = '`%s`' % cls.table_name
        return cls.__table_name_sql__

    @classproperty
    def column_names(cls):
        if not hasattr(cls, '__column_names__'):
            cls.__column_names__ = [ x.name for x in cls._get_db().get_table_by(cls.table_name).columns ]
        return cls.__column_names__

    @classproperty
    def column_name_set(cls):
        if not hasattr(cls, '__column_name_set__'):
            cls.__column_name_set__ = set(cls.column_names)
        return cls.__column_name_set__

    @classmethod
    def column_names_sql(cls, table_alias=None):
        if not hasattr(cls, '__column_names_sql__'):
            if table_alias:
                cls.__column_names_sql__ = ', '.join([ '%s.`%s` AS %s_r%s' % (table_alias, cn, table_alias, idx) for idx, cn in enumerate(cls.column_names) ])
            else:
                cls.__column_names_sql__ = ', '.join( map(lambda x: '`%s`' % x, cls.column_names) )
{'fraction_non_alphanumeric': 0.09647096739539131, 'fraction_numerical': 0.009453725089971532, 'mean_word_length': 2.292307692307692, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 33, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4000083', 'n_tokens_mistral': 5594, 'n_tokens_neox': 5188, 'n_words': 1451}