Rails 3 and clean URLs - ruby-on-rails

What is the most efficient way to map URLs to database IDs.
Example:
/newspaper/article/how-interesting-is-internet
In routing the newspaper_controller gets article and how-interesting-is-internet.
Where and how should I store the mapping for clean URLs and IDs?

FriendlyId is a good plugin for this (https://github.com/norman/friendly_id)
It allows you to specify a database column that will be used to create the id (name or description or whatever) and it takes care of making everything just work.

you should check out to_param method
class Article < AR::Base
def to_param
self.cool_url # cool_url is column in your articles table with your clean url
end
end
So my suggestion is to store your clean_url right in your Article model with your ID and other stuff

I like the friendlyId approach too
The way I handle this sort of thing using the clasic blog example
Blog Controller {Fetches all posts by date}
Posts Controller {}
Routes
resource :blog do
resources :posts
end
App/Models/Post.rb
class Post < ActiveRecord::Base
belongs_to :site
has_friendly_id :title, :use_slug => true
end
Then you end up with some nice paths
blog_path #Blog Index
blog_post(p) #Post Show

Use to_param method to create custom URLs such as http://myblog.com/posts/2012-04-22/123-my-first-post.html
class Post < ActiveRecord::Base
def to_param
"/posts/#{published_at}/{id}-#{title.parameterize}.html"
end
end

Related

add user 'name' to posts JSON with Ruby on Rails API

I have a Posts model which belongs to the User model in my Rails API repo.
I have added a column in the Posts table which is 'username'.
I would like to add the current_user.name to everypost in the username column.
Can I use delegate to add the User who creates the post's Name to the username column on my Post table.
I have already added the user_id as the reference which works.
class Post < ActiveRecord::Base
belongs_to :user
def as_json(options={})
super(only: [:description, :created_at, :user_id, :username])
end
end
This is only for use as a API and everytime I access the Post show route I would like the JSON to return the Users name that the posts belongs to in the JSON under :username
The idea behind relational databases like you are using it with ActiveRecord is that you don't have to take care of things like "copying" over the user names.
If your database schema says that every Post belongs to a User and you can call the User of a Post by calling:
post.user.username
The post you are trying to get the user from has to be.
Some good examples that might help you understand this further can be found here: http://guides.rubyonrails.org/association_basics.html
To get an understanding of how you could build out and structure your Rails API you could start with this guide: https://www.codementor.io/ruby-on-rails/tutorial/creating-simple-api-with-rails
You can use ActiveModelSerializers instead of override as_json.
Look at this article : https://robots.thoughtbot.com/better-serialization-less-as-json
gem 'active_model_serializers', '~> 0.10.0'
bundle install
rails g serializer post
rails g serializer user
Adds the attributes / relations that you want to expose.
class PostSerializer < ActiveModel::Serializer
has_one :user
end
class UserSerializer < ActiveModel::Serializer
attributes :username, :user_id
end
I simply changed the format of the Post JSON to include the User association:
def as_json(options={})
super(only: [:id, :description, :created_at, :user_id],
include: [user: { only: [:name] }]
)
end

Rails many to many model, adding records

Hello im trying to add some records to my database with this model
class Colleagueship < ActiveRecord::Base
belongs_to :employee
belongs_to :colleague, :class_name => 'Employee'
end
class Employee < ActiveRecord::Base
has_many :colleagueships
has_many :colleagues, :through => :colleagueships
# ...
end
but i have no idea in how to start a new form to create new records
im thinking to try something like
def new
employee = ## gotta get the id here in the form
#colleagueship = employee.colleagueships.build(:colleague_id => params[:colleague_id])
#colleagueship.save
end
what do you think? how do i achieve this with a post http method? do i have to save the employee variable with the request and add the employee_id there?
In the controller
def new
end
def create
# inspect submitted params here
puts params
if colleagueship.save
# etc etc
else
# error
end
end
private
def employee
#employee = Employee.find_by(params[:employee_id])
end
def colleagueship
#colleagueship = employee.colleageships.build
end
helper_method :employee, :colleagueship
Your routes should be nested to provide the key you'll use to find the employee.
resources :employees do
# this will generate /employees/:employee_id/colleagues/:id
resources :colleagueships
end
In your view, you will probably use the form_tag helper, as it's easier to customize forms with whatever fields you want, especially if you're avoiding accepts_nested_attributes which you should. You can also include a hidden_field_tag with employee_id if you aren't nested your routes.
= form_tag new_employee_colleague_path do
= text_field_tag 'colleageship[name]', placeholder: 'something...'
Something along these lines should work. Make sure to inspect the params hash to see that the values are formatted correctly.

How to hide records, rather than delete them (soft delete from scratch)

Let's keep this simple. Let's say I have a User model and a Post model:
class User < ActiveRecord::Base
# id:integer name:string deleted:boolean
has_many :posts
end
class Post < ActiveRecord::Base
# id:integer user_id:integer content:string deleted:boolean
belongs_to :user
end
Now, let's say an admin wants to "delete" (hide) a post. So basically he, through the system, sets a post's deleted attribute to 1. How should I now display this post in the view? Should I create a virtual attribute on the post like this:
class Post < ActiveRecord::Base
# id:integer user_id:integer content:string deleted:boolean
belongs_to :user
def administrated_content
if !self.deleted
self.content
else
"This post has been removed"
end
end
end
While that would work, I want to implement the above in a large number of models, and I can't help feeling that copy+pasting the above comparative into all of my models could be DRYer. A lot dryer.
I also think putting a deleted column in every single deletable model in my app feels a bit cumbersome too. I feel I should have a 'state' table. What are your thoughts on this:
class State
#id:integer #deleted:boolean #deleted_by:integer
belongs_to :user
belongs_to :post
end
and then querying self.state.deleted in the comparator? Would this require a polymorphic table? I've only attempted polymorphic once and I couldn't get it to work. (it was on a pretty complex self-referential model, mind). And this still doesn't address the problem of having a very, very similar class method in my models to check if an instance is deleted or not before displaying content.
In the deleted_by attribute, I'm thinking of placing the admin's id who deleted it. But what about when an admin undelete a post? Maybe I should just have an edited_by id.
How do I set up a dependent: :destroy type relationship between the user and his posts? Because now I want to do this: dependent: :set_deleted_to_0 and I'm not sure how to do this.
Also, we don't simply want to set the post's deleted attributes to 1, because we actually want to change the message our administrated_content gives out. We now want it to say, This post has been removed because of its user has been deleted. I'm sure I could jump in and do something hacky, but I want to do it properly from the start.
I also try to avoid gems when I can because I feel I'm missing out on learning.
I usually use a field named deleted_at for this case:
class Post < ActiveRecord::Base
scope :not_deleted, lambda { where(deleted_at: nil) }
scope :deleted, lambda { where("#{self.table_name}.deleted_at IS NOT NULL") }
def destroy
self.update(deleted_at: DateTime.current)
end
def delete
destroy
end
def deleted?
self.deleted_at.present?
end
# ...
Want to share this functionnality between multiple models?
=> Make an extension of it!
# lib/extensions/act_as_fake_deletable.rb
module ActAsFakeDeletable
# override the model actions
def destroy
self.update(deleted_at: DateTime.current)
end
def delete
self.destroy
end
def undestroy # to "restore" the file
self.update(deleted_at: nil)
end
def undelete
self.undestroy
end
# define new scopes
def self.included(base)
base.class_eval do
scope :destroyed, where("#{self.table_name}.deleted_at IS NOT NULL")
scope :not_destroyed, where(deleted_at: nil)
scope :deleted, lambda { destroyed }
scope :not_deleted, lambda { not_destroyed }
end
end
end
class ActiveRecord::Base
def self.act_as_fake_deletable(options = {})
alias_method :destroy!, :destroy
alias_method :delete!, :delete
include ActAsFakeDeletable
options = { field_to_hide: :content, message_to_show_instead: "This content has been deleted" }.merge!(options)
define_method options[:field_to_hide].to_sym do
return options[:message_to_show_instead] if self.deleted_at.present?
self.read_attribute options[:field_to_hide].to_sym
end
end
end
Usage:
class Post < ActiveRecord::Base
act_as_fake_deletable
Overwriting the defaults:
class Book < ActiveRecord::Base
act_as_fake_deletable field_to_hide: :title, message_to_show_instead: "This book has been deleted man, sorry!"
Boom! Done.
Warning: This module overwrite the ActiveRecord's destroy and delete methods, which means you won't be able to destroy your record using those methods anymore. Instead of overwriting you could create a new method, named soft_destroy for example. So in your app (or console), you would use soft_destroy when relevant and use the destroy/delete methods when you really want to "hard destroy" the record.

How could I query these 3 associated models?

Imagine these models:
class User
belongs_to :profile
# has email here
end
class Profile
has_one :user
# has first_name,last_name
end
and
class Post
belongs_to :profile
# has title,content
end
Now, I would like to query all posts ( do a LIKE "%substring%" ) on the user's email. I would prefer to not have to write it with map/selects as I think it would generate pretty inefficient code. I tried something like that:
class Post
def self.with_user_email_like(email)
self.joins(:profile).where("profile.email LIKE ?","%#{email}%")
end
end
The thing is, I know somehow I should have a profile.user.email in the condition above, but I just can't get it to work. Any suggestions?
Try this
class Post
belongs_to :profile
scope :with_user_email_like, lambda{|email| joins(:profile => :user).where("users.email LIKE %?%", email)}
end
Well you are almost there, but since email is in the users table you have to join that too:
self.joins(:profile => :user).where("users.email LIKE ?","%#{email}%")

Rails tip - "Use model association"

So, I've read in some book about tip "Use model association", which encourages developers to use build methods instead of putting ids via setters.
Assume you have multiple has_many relationships in your model. What's best practise for creating model then ?
For example, let's say you have models Article, User and Group.
class Article < ActiveRecord::Base
belongs_to :user
belongs_to :subdomain
end
class User < ActiveRecord::Base
has_many :articles
end
class Subdomain < ActiveRecord::Base
has_many :articles
end
and ArticlesController:
class ArticlesController < ApplicationController
def create
# let's say we have methods current_user which returns current user and current_subdomain which gets current subdomain
# so, what I need here is a way to set subdomain_id to current_subdomain.id and user_id to current_user.id
#article = current_user.articles.build(params[:article])
#article.subdomain_id = current_subdomain.id
# or Dogbert's suggestion
#article.subdomain = current_subdomain
#article.save
end
end
Is there a cleaner way ?
Thanks!
This should be a little cleaner.
#article.subdomain = current_subdomain
The only thing I can think of is merging the subdomain with params:
#article = current_user.articles.build(params[:article].merge(:subdomain => current_subdomain))

Resources