草庐IT

.NET应用系统的国际化-基于Roslyn抽取词条、更新代码

I love .net 2023-03-28 原文

上篇文章我们介绍了

VUE+.NET应用系统的国际化-多语言词条服务

系统国际化改造整体设计思路如下:

  1. 提供一个工具,识别前后端代码中的中文,形成多语言词条,按语言、界面、模块统一管理多有的多语言词条
  2. 提供一个翻译服务,批量翻译多语言词条
  3. 提供一个词条服务,支持后端代码在运行时根据用户登录的语言,动态获取对应的多语言文本
  4. 提供前端多语言JS生成服务,按界面动态生成对应的多语言JS文件,方便前端VUE文件使用。
  5. 提供代码替换工具,将VUE前端代码中的中文替换为$t("词条ID"),后端代码中的中文替换为TermService.Current.GetText("词条ID")

今天,我们在上篇文章的基础上,继续介绍基于Roslyn抽取词条、更新代码。

一、业务背景

先说一下业务背景,后端.NET代码中存在大量的中文提示和异常消息,甚至一些中文返回值文本。

这些中文文字都需要识别出来,抽取为多语言词条,同时将代码替换为调用多语言词条服务获取翻译后的文本。

例如:

private static void CheckMd5(string fileName, string md5Data)
{
      string md5Str = MD5Service.GetMD5(fileName);
      if (!string.Equals(md5Str, md5Data, StringComparison.OrdinalIgnoreCase))
      {
           throw new CustomException(PackageExceptionConst.FileMd5CheckFailed, "服务包文件MD5校验失败:" + fileName);
      }
}

代码中需要将“服务包文件MD5校验失败”这个文本做多语言改造。

这里通过调用多语言词条服务I18NTermService,根据线程上下文中设置的语言,获取对应的翻译文本。例如以下代码:

var text=T.Core.I18N.Service.TermService.Current.GetTextFormatted("词条ID""默认文本"); 

throw new CustomException(PackageExceptionConst.FileMd5CheckFailed, text + fileName);

以上背景下,我们准备使用Roslyn技术对代码进行中文扫描,对扫描出来的文本,做词条抽取、代码替换。

二、使用Roslyn技术对代码进行中文扫描

首先,我们先定义好代码中多语言词条的扫描结果类TermScanResult

 1  [Serializable]
 2     public class TermScanResult
 3     {
 4         public Guid Id { get; set; }
 5         public string OriginalText { get; set; }
 6 
 7         public string ChineseText { get; set; }
 8 
 9         public string SlnName { get; set; }
10 
11         public string ProjectName { get; set; }
12 
13         public string ClassFile { get; set; }
14 
15         public string MethodName { get; set; }
16 
17         public string Code { get; set; }
18 
19         public I18NTerm I18NTerm { get; set; }
20 
21         public string SlnPath { get; set; }
22 
23         public string ClassPath { get; set; }
24 28         public string SubSystemCode { get; set; }
29 
30         public override string ToString()
31         {
32             return Code;
33         }
34     }

上述代码中SubSystemCode是一个业务管理维度。大家忽略即可。

我们会以sln解决方案为单位,扫描代码中的中文文字。

以下是具体的实现代码

public async Task<List<TermScanResult>> CheckSln(string slnPath, System.ComponentModel.BackgroundWorker backgroundWorker, SubSystemFile subSystemFiles, string subSystem)
{
            var slnFile = new FileInfo(slnPath);
            var results = new List<TermScanResult>();

            MSBuildHelper.RegisterMSBuilder();
            var solution = await MSBuildWorkspace.Create().OpenSolutionAsync(slnPath);

            var subSystemInfo = subSystemFiles?.SubSystemSlnMappings.FirstOrDefault(w => w.SlnName.Select(s => s += ".sln").Contains(slnFile.Name.ToLower()));

            if (solution.Projects != null && solution.Projects.Count() > 0)
            {
                foreach (var project in solution.Projects.ToList())
                {
                    backgroundWorker.ReportProgress(10, $"扫描Project: {project.Name}");
                    var documents = project.Documents.Where(x => x.Name.Contains(".cs"));

                    if (project.Name.ToLower().Contains("test"))
                    {
                        continue;
                    }
                    var codeReplace = new CodeReplace();
                    foreach (var document in documents)
                    {
                        var tree = await document.GetSyntaxTreeAsync();
                        var root = tree.GetCompilationUnitRoot();
                        if (root.Members == null || root.Members.Count == 0) continue;
                        //member
                        var classDeclartions = root.DescendantNodes().Where(i => i is ClassDeclarationSyntax);

                        foreach (var classDeclare in classDeclartions)
                        {
                            var programDeclaration = classDeclare as ClassDeclarationSyntax;
                            if (programDeclaration == null) continue;

                            foreach (var memberDeclarationSyntax in programDeclaration.Members)
                            {
                                foreach (var item in GetLiteralStringExpression(memberDeclarationSyntax))
                                {
                                    var statementCode = item.Item1;
                                    foreach (var syntaxNode in item.Item3)
                                    {
                                        ExpressionSyntaxParser expressionSyntaxParser = new ExpressionSyntaxParser();
                                        var text = "";
                                        var expressionSyntax = expressionSyntaxParser
                                            .GetExpressionSyntaxVerifyRule(syntaxNode as ExpressionSyntax, statementCode);
                                        if (expressionSyntax != null)
                                        {
                                            // 排除
                                            if (expressionSyntaxParser.IsExcludeCaller(expressionSyntax, statementCode))
                                            {
                                                continue;
                                            }

                                            text = expressionSyntaxParser.GetExpressionSyntaxOriginalText(expressionSyntax, statementCode);
                                            if (expressionSyntax is Microsoft.CodeAnalysis.CSharp.Syntax.InterpolatedStringExpressionSyntax)
                                            {
                                                text = expressionSyntaxParser.GetExpressionSyntaxOriginalText(expressionSyntax, statementCode);

                                                if (expressionSyntax is Microsoft.CodeAnalysis.CSharp.Syntax.LiteralExpressionSyntax)
                                                {
                                                    if (!expressionSyntax.IsKind(SyntaxKind.StringLiteralExpression))
                                                    {
                                                        continue;
                                                    }
                                                    text = expressionSyntax.NormalizeWhitespace().ToString();
                                                }
                                            }
                                        }
                                        if (CheckChinese(text) == false) continue;
                                        if (string.IsNullOrWhiteSpace(text)) continue;
                                        if (string.IsNullOrWhiteSpace(text.Replace("\"", "").Trim())) continue;

                                        results.Add(new TermScanResult()
                                        {
                                            Id = Guid.NewGuid(),
                                            ClassPath = programDeclaration.SyntaxTree.FilePath,
                                            SlnPath = slnPath,
                                            OriginalText = text.Replace("\"", "").Trim(),
                                            ChineseText = text,
                                            SlnName = slnFile.Name,
                                            ProjectName = project.Name,
                                            ClassFile = programDeclaration.Identifier.Text,
                                            MethodName = item.Item2,
                                            Code = statementCode,
                                            SubSystemCode = subSystem
                                        });
                                    }
                                }
                            }
                        }
                    }
                }
            }

     return results;
}

上述代码中,我们先使用MSBuilder编译,构建 sln解决方案

MSBuildHelper.RegisterMSBuilder();
var solution = await MSBuildWorkspace.Create().OpenSolutionAsync(slnPath);

然后遍历solution下的各个Project中的class类

foreach (var project in solution.Projects.ToList())
var documents = project.Documents.Where(x => x.Name.Contains(".cs"));

然后遍历类中声明、成员、方法中的每行代码,通过正则表达式识别是否有中文字符

public static bool CheckChinese(string strZh)
{
            Regex re = new Regex(@"[\u4e00-\u9fa5]+");
            if (re.IsMatch(strZh))
            {
                return true;
            }
            return false;
}

如果存在中文字符,作为扫描后的结果,识别为多语言词条

results.Add(new TermScanResult()
{
        Id = Guid.NewGuid(),
        ClassPath = programDeclaration.SyntaxTree.FilePath,
        SlnPath = slnPath,
        OriginalText = text.Replace("\"", "").Trim(),
        ChineseText = text,
        SlnName = slnFile.Name,
        ProjectName = project.Name,
        ClassFile = programDeclaration.Identifier.Text,
        MethodName = item.Item2,
        Code = statementCode,        //管理维度                                  
        SubSystemCode = subSystem    //管理维度

TermScanResult中没有对词条属性赋值。

public I18NTerm I18NTerm { get; set; }

下一篇文章的代码中,我们会通过多语言翻译服务,将翻译后的文本放到I18NTerm 属性中,作为多语言词条。

三、代码替换

代码替换这块逻辑中,我们设计了一个类SourceWeaver,对上一步的代码扫描结果,进行代码替换

CodeScanReplace这个方法中完成了代码的二次扫描和替换
 /// <summary>
    /// 源代码替换服务
    /// </summary>
    public class SourceWeaver
    {
        List<CommonTermDto> commonTerms = new List<CommonTermDto>();
        List<CommonTermDto> commSubTerms = new List<CommonTermDto>();

        public SourceWeaver()
        {
            commonTerms = JsonConvert.DeserializeObject<List<CommonTermDto>>(File.ReadAllText("comm_data.json"));
            commSubTerms = JsonConvert.DeserializeObject<List<CommonTermDto>>(File.ReadAllText("comm_sub_data.json"));
        }
        public async Task CodeScanReplace(Tuple<List<I18NTerm>, List<TermScanResult>> result, System.ComponentModel.BackgroundWorker backgroundWorker)
        {
            try
            {
                backgroundWorker.ReportProgress(0, "正在对代码进行替换.");
                var termScanResultGroupBy = result.Item2.GroupBy(g => g.SlnName);
                foreach (var termScanResult in termScanResultGroupBy)
                {
                    var termScan = termScanResult.FirstOrDefault();
                    MSBuildHelper.RegisterMSBuilder();
                    var solution = await MSBuildWorkspace.Create().OpenSolutionAsync(termScan.SlnPath).ConfigureAwait(false);
                    if (solution.Projects.Any())
                    {
                        foreach (var project in solution.Projects.ToList())
                        {
                            if (project.Name.ToLower().Contains("test"))
                            {
                                continue;
                            }
                            var projectTermScanResults = result.Item2.Where(f => f.ProjectName == project.Name);

                            var documents = project.Documents.Where(x =>
                            {
                                return x.Name.Contains(".cs") && projectTermScanResults.Any(f => $"{f.ClassPath}" == x.FilePath);
                            });

                            foreach (var document in documents)
                            {
                                var tree = await document.GetSyntaxTreeAsync().ConfigureAwait(false);
                                var root = tree.GetCompilationUnitRoot();
                                if (root.Members.Count == 0) continue;

                                var classDeclartions = root.DescendantNodes()
                                    .Where(i => i is ClassDeclarationSyntax);
                                List<MemberDeclarationSyntax> syntaxNodes = new List<MemberDeclarationSyntax>();
                                foreach (var classDeclare in classDeclartions)
                                {
                                    if (!(classDeclare is ClassDeclarationSyntax programDeclaration)) continue;
                                    var className = programDeclaration.Identifier.Text;

                                    foreach (var method in programDeclaration.Members)
                                    {
                                        if (method is ConstructorDeclarationSyntax)
                                        {
                                            syntaxNodes.Add((ConstructorDeclarationSyntax)method);
                                        }
                                        else if (method is MethodDeclarationSyntax)
                                        {
                                            syntaxNodes.Add((MethodDeclarationSyntax)method);
                                        }
                                        else if (method is PropertyDeclarationSyntax)
                                        {
                                            syntaxNodes.Add(method);
                                        }
                                        else if (method is FieldDeclarationSyntax)
                                        {
                                            // 注:常量不支持
                                            syntaxNodes.Add(method);
                                        }
                                    }
                                }

                                var terms = termScanResult.Where(
                                    f => f.ProjectName == document.Project.Name && f.ClassPath == document.FilePath).ToList();
                                backgroundWorker.ReportProgress(10, $"正在检查{document.FilePath}文件.");
                                ReplaceNodesAndSave(root, syntaxNodes, terms, result, backgroundWorker, document.Name);
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                LogUtils.LogError(string.Format("异常类型:{0}\r\n异常消息:{1}\r\n异常信息:{2}\r\n",
                    ex.GetType().Name, ex.Message, ex.StackTrace));
                backgroundWorker.ReportProgress(0, ex.Message);
            }
        }

        public async void ReplaceNodesAndSave(SyntaxNode classSyntaxNode, List<MemberDeclarationSyntax> syntaxNodes, IEnumerable<TermScanResult> terms, Tuple<List<I18NTerm>, List<TermScanResult>> result,
            System.ComponentModel.BackgroundWorker backgroundWorker, string className)
        {

            {//check pro是否存在词条
                if (AppConfig.Instance.IsCheckTermPro)
                {
                    backgroundWorker.ReportProgress(15, $"词条验证中.");
                    var termsCodes = terms.Select(f => f.I18NTerm.Code).ToList();
                    var size = 100;
                    var p = (result.Item2.Count() + size - 1) / size;

                    using DBHelper dBHelper = new DBHelper();
                    List<I18NTerm> items = new List<I18NTerm>();
                    for (int i = 0; i < p; i++)
                    {
                        var list = termsCodes
                            .Skip(i * size).Take(size);
                        Thread.Sleep(10);
                        var segmentItems = await dBHelper.GetTermsAsync(termsCodes).ConfigureAwait(false);
                        items.AddRange(segmentItems);
                    }

                    List<TermScanResult> termScans = new List<TermScanResult>();
                    foreach (var term in terms)
                    {
                        if (items.Any(f => f.Code == term.I18NTerm.Code))
                        {
                            termScans.Add(term);
                        }
                        else
                        {
                            backgroundWorker.ReportProgress(20, $"词条{term.OriginalText}未导入到词条库,该词条将忽略替换.");
                        }
                    }
                    terms = termScans;
                }
            }

            var newclassDeclare = classSyntaxNode;
            newclassDeclare = classSyntaxNode.ReplaceNodes(syntaxNodes,
                    (methodDeclaration, _) =>
                    {                     
                        MemberDeclarationSyntax newMemberDeclarationSyntax = methodDeclaration;
                        var className = ((ClassDeclarationSyntax)newMemberDeclarationSyntax.Parent).Identifier.Text;
                        List<StatementSyntax> statementSyntaxes = new List<StatementSyntax>();

                        switch (newMemberDeclarationSyntax)
                        {
                            case ConstructorDeclarationSyntax:
                                {
                                    var blockSyntax = (newMemberDeclarationSyntax as ConstructorDeclarationSyntax).NormalizeWhitespace().Body;
                                    if (blockSyntax == null)
                                    {
                                        break;
                                    }
                                    foreach (var statement in blockSyntax.Statements)
                                    {
                                        var nodeStatement = statement.DescendantNodes();

                                        statementSyntaxes.Add(new CodeReplace().ReplaceStatementNodes(statement,
                                            new ExpressionSyntaxParser().LiteralStringExpression(nodeStatement), terms, commonTerms, commSubTerms));
                                    }

                                    break;
                                }

                            case MethodDeclarationSyntax:
                                {
                                    var blockSyntax = (methodDeclaration as MethodDeclarationSyntax).NormalizeWhitespace().Body;
                                    if (blockSyntax == null)
                                    {
                                        break;
                                    }
                                    foreach (var statement in blockSyntax.Statements)
                                    {
                                        var nodeStatement = statement.DescendantNodes();
                                        statementSyntaxes.Add(new CodeReplace().ReplaceStatementNodes(statement,
                                               new ExpressionSyntaxParser().LiteralStringExpression(nodeStatement), terms, commonTerms, commSubTerms));
                                    }

                                    break;
                                }

                            case PropertyDeclarationSyntax:
                                {
                                    var propertyDeclarationSyntax = newMemberDeclarationSyntax as PropertyDeclarationSyntax;

                                    var nodeStatement = propertyDeclarationSyntax.DescendantNodes();

                                    return new CodeReplace().ReplacePropertyNodes(newMemberDeclarationSyntax as PropertyDeclarationSyntax,
                                        new ExpressionSyntaxParser().LiteralStringExpression(nodeStatement), terms, commonTerms, commSubTerms);
                                }

                            case FieldDeclarationSyntax:
                                {
                                    var fieldDeclarationSyntax = newMemberDeclarationSyntax as FieldDeclarationSyntax;
                                    var nodeStatement = fieldDeclarationSyntax.DescendantNodes();
                                    return new CodeReplace().ReplaceFiledNodes(fieldDeclarationSyntax,
                                           new ExpressionSyntaxParser().LiteralStringExpression(nodeStatement), terms, commonTerms, commSubTerms);
                                }
                        }
                        backgroundWorker.ReportProgress(50, $"解析并对类文件{className}中的方法做语句替换.");
                        // 替换方法内部
                        if (newMemberDeclarationSyntax is MethodDeclarationSyntax)
                        {
                            return new CodeReplace().ReplaceMethodDeclaration(newMemberDeclarationSyntax as MethodDeclarationSyntax, statementSyntaxes);
                        }
                        else if (newMemberDeclarationSyntax is ConstructorDeclarationSyntax)
                        {
                            return new CodeReplace().ReplaceConstructorDeclaration(newMemberDeclarationSyntax as ConstructorDeclarationSyntax, statementSyntaxes);
                        }
                        return newMemberDeclarationSyntax;
                    });

            var sourceStr = newclassDeclare.NormalizeWhitespace().GetText().ToString();
            File.WriteAllText(newclassDeclare.SyntaxTree.FilePath, sourceStr);
            backgroundWorker.ReportProgress(100, $"完成{className}的替换.");
        }
    }

关键的代码语义替换的实现代码:

 public StatementSyntax ReplaceStatementNodes(StatementSyntax statement, List<ExpressionSyntax> expressionSyntaxes, IEnumerable<TermScanResult> terms
            , List<CommonTermDto> commonTerms, List<CommonTermDto> commSubTerms)
        {
            var statementSyntax = statement.ReplaceNodes(expressionSyntaxes, (syntaxNode, _) =>
            {
                var statementStr = statement.NormalizeWhitespace().ToString();

                var argumentLists = statement.DescendantNodes().
                                               OfType<InvocationExpressionSyntax>();
                ExpressionSyntaxParser expressionSyntaxParser = new ExpressionSyntaxParser();
                return expressionSyntaxParser.ExpressionSyntaxTermReplace(syntaxNode, statementStr, terms, commonTerms, commSubTerms);

            });

            return statementSyntax;
        }

这里,我们抽象了一个ExpressionSyntaxParser 类,负责替换代码:

T.Core.I18N.Service.TermService.Current.GetTextFormatted
 public ExpressionSyntax ExpressionSyntaxTermReplace(ExpressionSyntax syntaxNode, string statementStr, IEnumerable<TermScanResult> terms
            , List<CommonTermDto> commonTerms, List<CommonTermDto> commSubTerms)
        {
            var expressionSyntax = GetExpressionSyntaxVerifyRule(syntaxNode, statementStr);
            var originalText = GetExpressionSyntaxOriginalText(expressionSyntax, statementStr);

            var I18Expr = "";
            var interpolationSyntaxes = syntaxNode.DescendantNodes().OfType<InterpolationSyntax>();         
            var term = terms.FirstOrDefault(i => i.ChineseText == originalText);

            if (term == null)
                return syntaxNode;
            string termcode = term.I18NTerm.Code;
if (syntaxNode is InterpolatedStringExpressionSyntax)
            {
                if (interpolationSyntaxes.Count() > 0)
                {
                    var parms = "";
                    foreach (var item in interpolationSyntaxes)
                    {
                        parms += $",{item.ToString().TrimStart('{').TrimEnd('}')}";
                    }
                    I18Expr = "$\"{T.Core.I18N.Service.TermService.Current.GetTextFormatted(\"" + termcode + "\", " + originalText + parms + ")}\"";
                    var token1 = SyntaxFactory.Token(default, SyntaxKind.StringLiteralToken, I18Expr, "", default);
                    return SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, token1);
                }
                else
                {

                    var startToken = SyntaxFactory.Token(SyntaxKind.InterpolatedStringStartToken);
                    if ((syntaxNode as InterpolatedStringExpressionSyntax).StringStartToken.Value == startToken.Value)
                    {
                        // 如果本身有"$"
                        I18Expr = "$\"{T.Core.I18N.Service.TermService.Current.GetText(\"" + termcode + "\"," + originalText + ")}";
                    }
                    else
                    {
                        // 如果没有"$"
                        I18Expr = "$\"{T.Core.I18N.Service.TermService.Current.GetText(\"" + termcode + "\",\\teld\"" + originalText + "\")}";
                        I18Expr = I18Expr.Replace("\\teld", "$");
                    }
                }
            }
            else
            {
                I18Expr = "$\"{T.Core.I18N.Service.TermService.Current.GetText(\"" + termcode + "\"," + originalText + ")}";
            }

            var token = SyntaxFactory.Token(default(SyntaxTriviaList), SyntaxKind.InterpolatedVerbatimStringStartToken, I18Expr, "$\"", default(SyntaxTriviaList));
            var literalExpressionSyntax = SyntaxFactory.InterpolatedStringExpression(token);
            return literalExpressionSyntax;
        }
T.Core.I18N.Service.TermService这个就是多语言词条服务类,这个类中提供了一个GetText的方法,通过词条编号,获取多语言文本。

代码完成替换后,打开VS,对工程引用多语言词条服务的Nuget包/dll,重新编译代码,手工校对替换后的代码即可。
以上是.NET应用系统的国际化-基于Roslyn抽取词条、更新代码的分享。



周国庆
2023/3/19







有关.NET应用系统的国际化-基于Roslyn抽取词条、更新代码的更多相关文章

  1. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

    作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

  2. ruby - 将差异补丁应用于字符串/文件 - 2

    对于具有离线功能的智能手机应用程序,我正在为Xml文件创建单向文本同步。我希望我的服务器将增量/差异(例如GNU差异补丁)发送到目标设备。这是计划:Time=0Server:hasversion_1ofXmlfile(~800kiB)Client:hasversion_1ofXmlfile(~800kiB)Time=1Server:hasversion_1andversion_2ofXmlfile(each~800kiB)computesdeltaoftheseversions(=patch)(~10kiB)sendspatchtoClient(~10kiBtransferred)Cl

  3. ruby - 如何在 buildr 项目中使用 Ruby 代码? - 2

    如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby​​

  4. ruby-on-rails - Rails 源代码 : initialize hash in a weird way? - 2

    在rails源中:https://github.com/rails/rails/blob/master/activesupport/lib/active_support/lazy_load_hooks.rb可以看到以下内容@load_hooks=Hash.new{|h,k|h[k]=[]}在IRB中,它只是初始化一个空哈希。和做有什么区别@load_hooks=Hash.new 最佳答案 查看rubydocumentationforHashnew→new_hashclicktotogglesourcenew(obj)→new_has

  5. ruby-on-rails - Rails 应用程序之间的通信 - 2

    我构建了两个需要相互通信和发送文件的Rails应用程序。例如,一个Rails应用程序会发送请求以查看其他应用程序数据库中的表。然后另一个应用程序将呈现该表的json并将其发回。我还希望一个应用程序将存储在其公共(public)目录中的文本文件发送到另一个应用程序的公共(public)目录。我从来没有做过这样的事情,所以我什至不知道从哪里开始。任何帮助,将不胜感激。谢谢! 最佳答案 无论Rails是什么,几乎所有Web应用程序都有您的要求,大多数现代Web应用程序都需要相互通信。但是有一个小小的理解需要你坚持下去,网站不应直接访问彼此

  6. ruby - 无法运行 Rails 2.x 应用程序 - 2

    我尝试运行2.x应用程序。我使用rvm并为此应用程序设置其他版本的ruby​​:$rvmuseree-1.8.7-head我尝试运行服务器,然后出现很多错误:$script/serverNOTE:Gem.source_indexisdeprecated,useSpecification.Itwillberemovedonorafter2011-11-01.Gem.source_indexcalledfrom/Users/serg/rails_projects_terminal/work_proj/spohelp/config/../vendor/rails/railties/lib/r

  7. ruby-on-rails - Rails 应用程序中的 Rails : How are you using application_controller. rb 是新手吗? - 2

    刚入门rails,开始慢慢理解。有人可以解释或给我一些关于在application_controller中编码的好处或时间和原因的想法吗?有哪些用例。您如何为Rails应用程序使用应用程序Controller?我不想在那里放太多代码,因为据我了解,每个请求都会调用此Controller。这是真的? 最佳答案 ApplicationController实际上是您应用程序中的每个其他Controller都将从中继承的类(尽管这不是强制性的)。我同意不要用太多代码弄乱它并保持干净整洁的态度,尽管在某些情况下ApplicationContr

  8. ruby - 如何模拟 Net::HTTP::Post? - 2

    是的,我知道最好使用webmock,但我想知道如何在RSpec中模拟此方法:defmethod_to_testurl=URI.parseurireq=Net::HTTP::Post.newurl.pathres=Net::HTTP.start(url.host,url.port)do|http|http.requestreq,foo:1endresend这是RSpec:let(:uri){'http://example.com'}specify'HTTPcall'dohttp=mock:httpNet::HTTP.stub!(:start).and_yieldhttphttp.shou

  9. ruby-on-rails - 如何在我的 Rails 应用程序 View 中打印 ruby​​ 变量的内容? - 2

    我是一个Rails初学者,但我想从我的RailsView(html.haml文件)中查看Ruby变量的内容。我试图在ruby​​中打印出变量(认为它会在终端中出现),但没有得到任何结果。有什么建议吗?我知道Rails调试器,但更喜欢使用inspect来打印我的变量。 最佳答案 您可以在View中使用puts方法将信息输出到服务器控制台。您应该能够在View中的任何位置使用Haml执行以下操作:-puts@my_variable.inspect 关于ruby-on-rails-如何在我的R

  10. ruby-on-rails - 浏览 Ruby 源代码 - 2

    我的主要目标是能够完全理解我正在使用的库/gem。我尝试在Github上从头到尾阅读源代码,但这真的很难。我认为更有趣、更温和的踏脚石就是在使用时阅读每个库/gem方法的源代码。例如,我想知道RubyonRails中的redirect_to方法是如何工作的:如何查找redirect_to方法的源代码?我知道在pry中我可以执行类似show-methodmethod的操作,但我如何才能对Rails框架中的方法执行此操作?您对我如何更好地理解Gem及其API有什么建议吗?仅仅阅读源代码似乎真的很难,尤其是对于框架。谢谢! 最佳答案 Ru

随机推荐