I want to have a site that is a simple blog so I created a model:
class Post < ActiveRecord::Base
attr_accessible :title, :body
end
I want to use Markdown but without HTML tags. I also want always to keep database clean and my idea is to use before_save()/before_update() callbacks to sanitise my input and escape HTML.
I don't care about caching and performance therefore I always want to render post when needed. My idea is toadd following to the model:
def body_as_html
html_from_markdown(body)
end
What do you think of such design? MVC and ActiveRecord are new for me and I am not sure of used callback.
I see nothing obvious wrong with that method. Caching is a very simple thing to enable if performance becomes an issue... the important thing to make caching useful is to reduce or eliminate the dynamic content on the page, so that the cache doesn't constantly get obsolete. If you're just showing the blog post, then the cache only needs to be regenerated if the blog changes, or perhaps if someone adds a comment (if you have comments).
My general rule of thumb is to keep the data in your database as "pure" as possible, and do any sanitization, rendering, escaping or general munging as close to the user as possible - typically in a helper method or the view, in a Rails app.
This has served me well for several reasons:
Different representations of your data may have display requirements - if you implement a console interface at some point, you won't want to have all that html sanitization.
Keeping all munging as far out from the database as possible makes it clear whose responsibility it is to sanitize. Many tools or new developers maintaining your code may not realize that strings are already sanitized, leading to double-escaping and other formatting ugliness. This also applies to the "different representations" problem, as things can end up escaped in multiple different ways.
When you look in your database by hand, which will end up happening from time to time, it's nice to see things in their un-munged form.
So, to address your specific project, I would suggest having your users enter their text as Markdown and storing it straight in to the database, without the before_save hook (which, as an aside, would be called on creation or update, so you wouldn't also need a before_update hook unless there was something specific that you wanted on update but not creation). I would then create a helper method, maybe santize_markdown, to do your sanitization. You could then call your helper method on the raw markdown, and generate your body html from the sanitized markdown. This could go in the view or in another helper method according to your taste and how many different places you were doing it, but I probably wouldn't put it in the model since it's so display-specific.
Related
I'm in the process of updating a website I made almost 2 years ago. It was my first real website and I made some mistakes (some more serious that others).
What apparently is one of my biggest is making database calls from the view.
I'm pretty damn sure there is a better way to do this:
Use Case:
Someone fills out a form for a new subject, populating the Subject table, and they have been marked "enrolled", Subject.enrolled = 1
Based on that, I now need to create a record in 5 other tables (such as Baseline)
Downhill from here, here is my method
Determine if the record exist based on subject_id from Subject (sub)
<$ if Baseline.where(subject_id: sub.subject_id).first != nil $>
If it does not exist, create the record, (otherwise display the link)
<%= Baseline.create(subject_id: sub.subject_id) %>
This all happens in the view, and creates a front-end table with links to each record in the process. So I'm creating records based on for-loop logic...
Question:
So I'm looking for direction. I don't want to guess how to do this - I'm pretty sure the model/controller should do this - I want to learn how to do it correctly. How do I create records automatically, based on a value in a table?
Thank you for your time.
Not quite sure how your domain and code looks like, but to answer this question: 'How do I create records automatically, based on a value in a table?', it seems that you could use ActiveRecord callbacks, like this:
class Subject < ActiveRecord::Base
after_commit :create_baseline_if_enrolled, on: [:create, :update]
private
def create_baseline_if_enrolled
return unless enrolled?
# enrolled? == true, you may create these models here
end
end
To answer your question:
It depends :) This is just one possible solution. Another one would be to put such a custom logic in your SubjectsController and call it directly from the #create, #update methods. Both approaches have pros and cons. For example, abusing callbacks (anywhere) makes code less readable and harder to debug. On the other hand, putting such logic in controllers puts a burden on you that you have to remember about calling it if you happen to be editing subjects in other places (but is more explicit). Whichever way you choose, remember not to make your classes too fat, for example try to use service object pattern to separate such custom logic as soon as you feel like it is getting out of hand. :) And don't forget about tests - when things go wrong, tests make refactoring easier.
So basically I have a Book model that contains information like
book title (string)
author (string)
description (text)
etc.
Now on the front end I added the capability of dynamically adding more author fields. In the form I named it book[coauthor], so if I add a second author, I'd have a field book[coauthor][name_0].
What I'd like to achieve is combine book[author] and all book[coauthor][name_i] to one big string and separate each name with ,. (i.e. so with book[author] being "Alice" and book[coauthor][name_0] being "Bob" I should get "Alice,Bob" saved in book[author] in DB)
First Question: At this point is it better to do it with Javascript or with Rails (in controller)?
Second Questiion: Currently I'm doing it in the Rails controller but got Can't mass-assign protected attribute 'coauthor'. However I don't want to make it a real attribute since all I need is some processing before saving all the information to the author field. What should I do?
Thanks guys.
The first question is a bit loaded, and you might get some heated disagreements but the way I look at it is this. Not everybody runs with Javascript enabled, so I make sure I have a way to do everything through Rails myself.
In this case, it's actually quite easy.
The first thing I recommend is to not send the coauthors as part of the params[:book]. Instead, send them separately as a params[:coauthors]. Then in your controller you can do this:
#book = Book.new(params[:book])
#book.author = ([#book.author] + [params[:coauthors]]).join(",")
#book.save
Actually, it may be better to update params[:book][:author] since that would work for both create and update. Either way, I hope that helps.
To the first question: I don't see any compelling reason to one over the other. JavaScript might provide a bit more ability to validate/fix formatting in this field in 'real time', but I'm not sure that's particularly important.
To the second question: You don't need to make something a real attribute in order to make it accessible. You've presumably created coauthor as a virtual attribute using attr_accessor, but this doesn't automatically add it to the mass-assignment whitelist. To do that, also add it to your attr_accessible list.
I'm having a very hard time trying to figure out how to do this the MVC way. I have a Comment model which holds a body attribute. This attribute may contain mentions such as the following:
Hi! This is me mentioning #someone.
Everytime someone posts a comment, an accessor method in the model converts all #mention to something like #user:231# where 231 would be the user's id. This way, if the mentioned user changes their username, I can still link & mention him without problems on older comments.
Now, I want to be able to access the body attribute and get the mentions already converted to links. It appears that doing this the MVC way, from within the model is not possible from what I have investigated.
Is there any easy way to do this? I don't wanna have to convert all the mentions on the controller because I think it could lead to repeated code and non-testable code.
Could anyone give me some advice on this?
Thanks!
Parsing the message into a particular format and then re-saving it in the database where it can then be edited at a later date is silly. I'm sorry to be so blunt, but doing it this way is fundamentally broken for one major reason: when a user goes to edit the message later on, they'll see the formatted text unless you format it back. Do you really want to be responsible for doing this?
I would hope not. Because you're a programmer, you're naturally lazy and would like to do things in as few steps as possible.
What I would recommend doing to solve this problem is to parse the message when you display it on the page. Before you go screaming at me that this is computationally intensive if you've got a large amount of hits, hear me out. When it's displayed on the page, you can then cache it like this:
<% cache comment do %>
# code goes here
<% end %>
This will store the final output in whatever cache you've set up with Rails, possibly Memcached or Redis, using a cache key which includes the comment's updated_at timestamp. Pay attention to this, it'll be useful later.
Retrieval from this cache will be faster than parsing it, and will be easier for you than to convert the message back and forth between its versions.
When a comment is updated, the updated_at timestamp will be different and so the new comment will be rendered first, then cached. In Memcached (so I'm told) it will clear the oldest cache key that hasn't been referenced if it needs more memory, thereby cleaning out the older comments.
Wouldn't you end up mangling the original message? Let's say I originally posted:
"Hi! This is me mentioning #bob."
From what I understand, you want to store this as:
"Hi! This is me mentioning #user:1#"
Now, if bob were to change his username to "fred", my message would now look like this:
"Hi! This is me mentioning #fred"
It may be easier to simply store a many-to-many relation between messages and users it mentions. That way, you still can easily see which messages mention a specific user, but you don't need to mangle the original message to do so.
If you need to convert each mention into a link, you could order the entries in the relationship table in the same order that they appear in the message.
maybe this gem help you https://github.com/twitter/twitter-text-rb
First, include Twitter::Autolink module from your class or helper
module ApplicationHelper
include Twitter::Autolink
end
From views, you can call it by :
<%= auto_link("Hi #john_doe, welcome to #ruby") %>
it will generate link to twitter john_doe username and ruby hashtag
I'm building a model, which whenever the model is run I want to inject into a news feed model. My idea was to create a after_save in the model and then use a helper to populate the feed details. But my model isn't finding the helper...
Now I'm wondering, what are helpers for, are helpers really just for Views or?
In my model I want to run stuff like:
newsfeed.feeded_id = record.id
newsfeed.feeded_type = record.class.name
Which is common to many models in my app. Where should that live, and how should that be accessed? Thanks
Helpers are primarily for views. They allow you to keep your code out of the views and make a testable chunk of code that can be used when rendering the view. It also allows you to reuse bits of code.
I don't think there are any rules of thumb, but if you use the exact same bit of view code more than once, consider abstracting it into a partial. If you have any complicated conditionals or calculations, consider placing it in a helper.
As an example of what's good to put in helpers, you need look no further than Rails itself. Perfect examples are link_to, form_for, etc. These take a bit of doing to construct but provide way more functionality than writing the HTML from scratch each time.
How about using an observer?
Are you setting up your news via RSS/ATOM? Your builder view might be a place to select what items you want in your feed.
I'm still new to ROR, so pardon the simplicity of the question...
So http://www.example.com/controller/:id displays a record in my table, with :id being a number (1,2,3 etc.).
Is there a way I can have :id in the URL be the value of a field in the displayed record? Such that I can have http://www.example.com/controller/record_field? I want to have a human-friendly reference to specific records in my table. I'm sure this must be possible. Do I change something in routes.rb?
Thanks for the help!
The cleanest way is to add a new find method in your model (or simply use the find_by_fieldname Rails gives you in your control). Then you'll have your controller use that method instead of the regular find(params[:id]) to pull your model record.
Check out Ryan B's screencast on this here. It's pretty easy, and he's a good teacher, so you shouldn't have any problems.
I use the excellent rails plugin named friendly_id.
http://github.com/norman/friendly_id/tree/master
That should sort you out nicely. It is well documented too.
Take care around fields that might have modern Greek characters—might need to figure a work around for those.
Jon Smock's solution will work, too. I tend to prefer the following.
class Hamburger << ActiveRecord::Base
#this normally defaults to id
def to_param
name
end
end
class SomeModelController << ApplicationController
def show
#hamburger = Hamburger.find(params[:id]) #still default code
end
end
#goes in some view
This is the <%= link_to "tastiest hamburger ever", url_for(#hamburger) %>.
This is, loosely speaking, an SEO technique (beautiful URLs are also user-friendly and I suggest them to absolutely everyone even if you don't care about SEO, for example on pages behind a login). I have a more extended discussion of Rails SEO, which includes other tips like this, here.
Important tip: You should consider, at design-time, what you are going to do if the param should change. For example, in my hamburger scenario, it is entirely possible that I might rename "Sinfully Delicious Cheeseburger" to "Triple Bypass". If that changes URLs, there are some possible implications, such as breakage of customer links to my website. Accordingly, for production use I usually give these models an immutable permalink attribute which I initialize to be human-meaningful exactly once. If the object later changes, oh well, the URL stays the same. (There are other solutions -- that is just the easiest one.)