Ruby on Rails - Truncate to a specific string - ruby

Clarification: The creator of the post should be able to decide when the truncation should happen.
I implemented a Wordpress like [---MORE---] functionality in my blog with following helper function:
# application_helper.rb
def more_split(content)
split = content.split("[---MORE---]")
split.first
end
def remove_more_tag(content)
content.sub(“[---MORE---]", '')
end
In the index view the post body will display everything up to (but without) the [---MORE---] tag.
# index.html.erb
<%= raw more_split(post.rendered_body) %>
And in the show view everything from the post body will be displayed except the [---MORE---] tag.
# show.html.erb
<%=raw remove_more_tag(#post.rendered_body) %>
This solution currently works for me without any problems.
Since I am still a beginner in programming I am constantly wondering if there is a more elegant way to accomplish this.
How would you do this?
Thanks for your time.
This is the updated version:
# index.html.erb
<%=raw truncate(post.rendered_body,
:length => 0,
:separator => '[---MORE---]',
:omission => link_to( "Continued...",post)) %>
...and in the show view:
# show.html.erb
<%=raw (#post.rendered_body).gsub("[---MORE---]", '') %>

I would use just simply truncate, it has all of the options you need.
truncate("And they found that many people were sleeping better.", :length => 25, :omission => '... (continued)')
# => "And they f... (continued)"
Update
After sawing the comments, and digging a bit the documentation it seems that the :separator does the work.
From the doc:
Pass a :separator to truncate text at a natural break.
For referenece see the docs
truncate(post.rendered_body, :separator => '[---MORE---]')
On the show page you have to use gsub

You could use a helper function on the index page that only grabs the first X characters in your string. So, it would look more like:
<%= raw summarize(post.rendered_body, 250) %>
to get the first 250 characters in your post. So, then you don't have to deal w/ splitting on the [---MORE---] string. And, on the show page for your post, you won't need to do anything at all... just render the post.body.
Here's an example summarize helper (that you would put in application_helper.rb):
def summarize(body, length)
return simple_format(truncate(body.gsub(/<\/?.*?>/, ""), :length => length)).gsub(/<\/?.*?>/, "")
end

I tried and found this one is the best and easiest
def summarize(body, length)
return simple_format = body[0..length]+'...'
end
s = summarize("to get the first n characters in your post. So, then you don't have to deal w/ splitting on the [---MORE---] post.body.",20)
ruby-1.9.2-p290 :017 > s
=> "to get the first n ..."

Related

How to replace words inside template placeholders

I'm trying to write a regular expression to replace <%= Name %> with "Some Person".
I'm using a regex because I want to modify it so that I don't have to worry about the spaces between = and Name as well as the E in Name and the %>
I tried:
body = %q(
Hello <%= Name %>,
This is a test. hello test
some more stuff here
and here.
<%= Name %>
)
parsed_body = body.gsub(/\A<%= Name %>\Z/, "Some person")
puts parsed_body
When parsed_body is printed out, the string is unchanged. What is wrong with my regex?
In your Regex, you have added the \A and \z anchors. These ensure that your regex only matches, if the string only contains exactly <%= Name %> with nothing before or after.
To match the your pattern anywhere in the string, you can simply remove the anchors:
parsed_body = body.gsub(/<%= Name %>/, "Some person")
Just another option considering what I am assuming you are trying to accomplish
tag_pattern = /(?<open_tag><%=\s*)(?<key>(\w+))(?<close_tag>\s*%>)/
body = <<-B
Hello,
My name is <%= Name %> and I know some things. Just because I am a
<%= Occupation %> doesn't mean I know everything, but I sure can
<%=Duty%> just as well as anyone else.
Also please don't replace <%= This %>
Thank you,
<%= Name %>
B
dict = {"Name" => "engineersmnky", "Occupation" => "Monkey", "Duty" => "drive a train"}
body.gsub(tag_pattern) {|m| dict[$2] || m }
#=> Hello,
# My name is engineersmnky and I know some things. Just because I am a
# Monkey doesn't mean I know everything, but I sure can
# drive a train just as well as anyone else.
# Also please don't replace <%= This %>
#
# Thank you,
# engineersmnky
In this case I used a dictionary of the anticipated portions of the "erb" to be replaced and used the block style of String#gsub to handle the replacements where $2 is the named capture key. When there is not a matching key it just leaves the match untouched e.g. "Also please don't replace <%= This %>"
You could implement this with any pattern you choose but if you are going to use "erb" style lines maybe try leveraging erb other wise the same will work below:
tag_pattern = (?<open_tag>{!!\s*)(?<key>(\w+))(?<close_tag>\s*!\?})
body = <<-B Hello,
My name is {!! Name !?} and I know some things. Just because I am a
{!! Occupation !?} doesn't mean I know everything, but I sure can
{!!Duty !?} just as well as anyone else.
Thank you,
{!! Name !?}
B
As long as you define tag_pattern correctly the replacement is fairly simple. Rubular Example
It looks like you're trying to write your own template parser, which is asking for a lot more trouble that it's worth considering those already exist.
However, this is the basic idea for such a thing:
erb = <<EOT
Owner: <%= name %>
Location: <%= address %>
EOT
FIELD_DATA = {
name: 'Olive Oyl',
address: '5 Sweethaven Village'
}
FIELD_RE = Regexp.union(FIELD_DATA.keys.map(&:to_s)).source # => "name|address"
puts erb.gsub(/<%=\s+(#{FIELD_RE})\s+%>/) { |k|
k # => "<%= name %>", "<%= address %>"
k[/\s+(\S+)\s+/, 1] # => "name", "address"
FIELD_DATA[k[/\s+(\S+)\s+/, 1].to_sym] # => "Olive Oyl", "5 Sweethaven Village"
}
Which, when run, outputs:
Owner: Olive Oyl
Location: 5 Sweethaven Village
This works because gsub can take a regular expression and a block. For every match of the expression it passes in the match to the block, which is then used to return the actual value being substituted in.
If you have a lot of target values, rather than use Regexp.union, instead use the RegexpTrie gem. See "Is there an efficient way to perform hundreds of text substitutions in Ruby?" for more information.
Again, template parsers exist, they've been around a long time, they're very well tested, and they handle edge cases you haven't thought about, so don't write a new partially-implemented one, instead reuse an existing one.

Rails 3.1.4 - Render :text

I'm updating my rails 2 apps to rails 3 and find that the use of 'render :text' does not behave the same anymore.
#results is an array. In my controller:
render :text => "<ul>#{#results}</ul>"
It's returning the whole array as a string rather than iterating through each value:
<ul>
["
<li>Steve</li>
", "
<li>John</li>
"]
</ul>
Worked fine in Rails 2.x but not in 3. How do I fix this?
I'm expecting a result of:
<ul>
<li>Steve</li>
<li>John</li>
</ul>
I know this question is for Rails 3.1.4 only.
But those who come here and are on a more recent version, starting with Rails 5.1 we'll do this:
render plain: "I'm like everyone else."
The string contains HTML tags so you will need to mark it as safe so that Rails doesn't escape the tags.
render :text => "<ul>#{#results}</ul>".html_safe
NOTE: Unless there is a valid reason to have HTML in your controller, I recommend moving the list items to a view.
6/23/2014 UPDATE: In retrospect, I don't like having this string parsing logic in the controller. The #results suggests there is HTML embedded in an object somewhere. I recommend using a presentation object and call a method like #results.list. The Draper gem is well-suited for this.
Cite
https://github.com/drapergem/draper
I would suggest doing the following instead of render :text
render :partial => "result", :collection => #results
and add the file: _result.html.erb with
<ul>
<%= result %>
</ul>
or even better if you can remove the li tags from #results
<ul>
<li><%= result %></li>
</ul>
The Rails 3 docs say render text should be used for NON HTML text, which does not fit your use case. Using render :partial :collection is a better and more rails 3 way to iterate through your list.

rails 3.1 simple_format html options

Reading up here I find that I can use the simple_format text helper to send a text string to keep html newlines. Great. But I find that since it assumes a tag that it messes with the CSS class in Blueprint CSS. OK, reading up on the API I find this:
simple_format(text, html_options={}, options={})
simple_format("I'm allowed! It's true.", {}, :sanitize =>
false)
=> "I'm allowed! It's true."
There's no other example using an html_option that I can find. Here are two attempts to make it work, but both fail, either with syntax errors or app errors.
<
%=simple_format(#opening.bob, html_options={"class="base prepend-1""},
:sanitize => false) %>
true) %>
I've tried a bunch of fooling around with different combinations but due to my inexperience, I'm not hitting the right one. What am I not seeing? thanx, sam
While I still haven't gotten this to work using html options, I failed to see the relevance of the example above it:
simple_format("Look ma! A class!", :class => 'description')
=> "Look ma! A class!"
It was pointed out to me that this is a hash and the class indicated is a CSS attribute, not a Rails attribute.
'base prepend-1') %>
using my variables above.

Is sprintf incompatible with sinatra?

Say I have this:
class Account
...
property :charge, Decimal, :precision => 7, :scale => 2
...
classy stuff
...
def self.balance(prefix)
x = Account.get(prefix.to_sym).order(:fields => [:charge]).sum(:charge)
sprintf("%5.2f", x)
end
end
(Edit: The value of all :charge fields is 0.13E2 (0.1E2 + 0.3E1). This is correctly returned. Only in a View does it seem to get borked from sprintf)
In IRB Account.balance(:AAA) returns => "13.00"
if I call Account.balance(:AAA) from a view I get TypeError at /accounts
can't convert nil into Float
Account.balance(:AAA) works anywhere I call it except in a view. If I remove sprintf("%5.2f", x) I get 0.13E2 in my view. (using Account.balance(:AAA).to_f in a view gives me 13.0)
Is sinatra incompatible with sprintf? or am I not understanding how to use sprintf?
(Edit: This is the offending view:)
<section>
<% #accounts.each do |account| %>
<article>
<h2><%= account.prefix %></h2>
<span><p>This account belongs to <%= account.name %> & has a balance of $<%= Account.balance(account.prefix) %>.</p></span>
</article>
<% end %>
</section>
Wouldn't it make more sense to define balance as an instance method rather than a class method? It looks from your example like you're calling balance in an account-specific way anyway, so why not make it:
# the model
class Account
#...
def balance
amount = self.order(:fields => [:charge]).sum(:charge)
sprintf "%5.2f", amount
# or the infix version:
"%5.2f" % amount
end
end
,
# the view
...balance of $<%= account.balance %>...
I know that this doesn't address sprintf per se, but the problem is more likely to be coming from the slightly convoluted lookup than from a built-in method. Even if my specific code doesn't suit your application, it might be worth simplifying the lookup step, even if that involves a few more lines of code.
The advantage of this approach is that there is no doubt that you'll be getting the right Account record.
tested it with a little sinatra app and it worked for me
app.rb
#!/usr/bin/env ruby
require 'rubygems'
require 'sinatra'
get '/' do
#x = 10.23
erb :index
end
views/index.erb
<%= sprintf("%5.2f", #x) %>
output:
10.23
ruby 1.9.2 / sinatra 1.3.1
I think there is another error before the sprintf because of your error message:
can't convert nil into Float
seems like your x is nil. try to be sure that x is not nil there, then sprintf should work as expected.

How do I get an array of check boxes in haml?

I have an array of strings, called #theModels, in a routine implemented as part of a Sinatra server. These models are options for the user to select, and are obtained by the back end (the idea being, as new models are added, then the front end code should not change).
I'm using haml to render html.
How can I enumerate each element in the list of #theModels such that each element is a checkbox? And how can I obtain which checkboxes the user has selected?
I see that just putting
= #theModels
will give me the list of strings contained in #theModels, but without spacing or the like, and certainly not in checkboxes. I've found this question that appears to be similar, but my haml-fu isn't good enough to convert that into what I need.
UPDATE:
These are options associated with a file upload, such that now the code looks like:
%form{:action=>"/Upload",:method=>"post",:enctype=>"multipart/form-data"}
- #theModelHash.each do |key,value|
%br
%input{:type=>"checkbox", :name=>"#{key}", :value=>1, :checked=>value}
=key
%input{:type=>"file",:name=>"file"}
%input{:type=>"submit",:value=>"Upload"}
Problem is, that puts a file upload button on each option, instead of at the end. I only want one submit button in the end; should I have two forms that both report their results when the 'Upload' button is pressed?
UPDATE2:
After a moment's thought, the above can be modified to:
Thanks!
%form{:action=>"/Upload",:method=>"post",:enctype=>"multipart/form-data"}
- #theModelHash.each do |key,value|
%br
%input{:type=>"checkbox", :name=>"#{key}", :value=>1, :checked=>value}
=key
%form{:action=>"/Upload",:method=>"post",:enctype=>"multipart/form-data"}
%input{:type=>"file",:name=>"file"}
%input{:type=>"submit",:value=>"Upload"}
And that appears to do what I want.
I think you should send the content as an hash instead.
This will give you the opportunity to set initial values in the form.
The hash #params will give you the result.
E.g. {"oranges"=>"1"}
#app.haml
%form{:method => 'post', :action => "/"}
- #models.each do |key,value|
%br
%input{:type=>"checkbox", :name=>"#{key}", :value=>1, :checked=>value}
=key
%input{:type => :submit, :value => "Save"}
#app.rb
require 'sinatra'
require 'haml'
get '/' do
#models = {"oranges" => true, "bananas" => false}
haml :app
end
post '/' do
#params.inspect
end
The link you provided linked to a rails solution where you have a function returning the proper html.
You can define this function yourself:
Input: key, value
Output: %input{:type=>"checkbox", :name=>"#{key}", :value=>1, :checked=>value}
def check_box(key, value)
...
end
and call it in haml with
=check_box(key,value)

Resources