2 save to update my alias with friendly ID - ruby-on-rails

I'm using the gem Friendly ID, and I currently have to do 2 saves in order to save the slug.
It doesn't seems right to me.
class Contractor < ActiveRecord::Base
include FriendlyId
friendly_id :slug_candidates, use: :slugged, slug_column: :alias
after_commit :generate_new_alias, unless: Proc.new {|contractor| contractor.business_name_changed? }
def slug_candidates
[
:business_name,
[:business_name, :city],
[:business_name, :city, :state]
]
end
def generate_new_alias
if self.alias != self.alias_was
self.alias = nil
self.save
end
end
end
Any idea what I'm doing wrong ?

Related

How can I dynamically determine which column is passed to friendly_id in order to watch it for changes?

I'm using the very useful option:
# config/initializers/friendly_id.rb
# ...
config.use Module.new {
def should_generate_new_friendly_id?
slug.blank?
end
}
The issue is that I use friendly_id on 3 or 4 different models for which the attribute to watch for a change is differrent. For some it's name, others title. Is there a way to dynamically retrieve the attribute symbol that was passed to friendly_id :title, use: :slugged to prevent this being necessary in models?
# app/models/foo.rb
class Foo < ActiveRecord::Base
# ...
friendly_id :title, use: :slugged
def should_generate_new_friendly_id?
super || title_changed?
end
end
```
Ideally I'm looking for something like:
# config/initializers/friendly_id.rb
# ...
config.use Module.new {
def should_generate_new_friendly_id?
slug.blank? || friendly_id_attribute_changed?
end
}
Can this be achieved?

Joint query across 2 models (has_many)

Hi I need help and all insight appreciated. I have two models Auctions and Bids and I want to retrieve the All auctions current_user won, the ones s/he has been outbid on and the ones s/he's winning
Here are the two models:
class Auction < ActiveRecord::Base
extend FriendlyId
friendly_id :guid, use: :slugged
before_save :populate_guid
mount_uploaders :images, ImageUploader
belongs_to :client
has_many :bids, dependent: :destroy
has_one :order, dependent: :destroy
validates_presence_of :title, :lien_price,
:end_time, :collateral_value,
:redemption_date, :current_interest_rate,
:additional_tax, :collateral_details,
:location, :client_id, :starting_bid
validate :end_time_in_the_future, :on => :update
validates_uniqueness_of :guid, case_sensitive: false
def end_time_in_the_future
errors.add(:end_time, "can't be in the past") if self.end_time && self.end_time < Time.now
end
def self.get_active_auctions
where("end_time > ?", Time.now)
end
def self.closed_auctions
where("end_time < ?", Time.now)
end
def highest_bid
self.bids.maximum("amount")
end
def highest_bid_object
self.bids.order(:amount => :desc).limit(1).first
end
def highest_bidder
self.highest_bid_object.user if highest_bid_object
end
def closed?
self.end_time < Time.now
end
private
def populate_guid
if new_record?
while !valid? || self.guid.nil?
self.guid = SecureRandom.random_number(1_000_000_000).to_s(36)
end
end
end
end
and
class Bid < ActiveRecord::Base
extend FriendlyId
friendly_id :guid, use: :slugged
belongs_to :auction
belongs_to :user
before_save :populate_guid
validates_presence_of :amount, :user_id,
:auction_id
#validate :higher_than_current?
validates :amount, :numericality => true
validates_uniqueness_of :guid, case_sensitive: false
def higher_than_current?
if !Bid.where("amount > ? AND auction_id = ?", amount, self.auction.id).empty?
errors.add(:amount, "is too low! It can't be lower than the current bid, sorry.")
end
end
private
def populate_guid
if new_record?
while !valid? || self.guid.nil?
self.guid = SecureRandom.random_number(1_000_000_000).to_s(36)
end
end
end
end
I thought
#auctions = Auction.closed_auctions.where(highest_bidder: current_user)
or
#auctions = Auction.closed_auctions.joins(:bids).where(highest_bidder: current_user)
would work but they both raise an error.
Edit this works
#auctions = Auction.closed_auctions.references(highest_bidder: current_user)
But there's probably a better way.
You probably can't access current_user from controller (devise?). So you need to pass the user as a parameter to the class or instance method. What you should look into are scopes and especially scopes that accept parameters. Scopes could really help you refactor your Auction model (you really don't need any methods that only return a where()), but also solve the inaccessible current_user.
Use it like this in your Auction model:
scope: :highest_bidder -> (current_user) { where(highest_bidder: current_user) }
And call it like this from your controller:
#auctions = Auction.closed_auctions.highest_bidder(current_user)

NoMethodError at / undefined method `name' for nil:NilClass

I have tried many solution and came up with good one but still getting error. I am editing my whole question.
I am trying to create Friendly URL with friendly_id gem.
In my project First user need to signup with devise.
Devise will pass some information to profile model with
model/user.rb
delegate :age, :city, :region, :country, to: :profile
I want to make user.name to be Friendly_id candidate. I have tried following code in my Profile model:-
class Profile < ActiveRecord::Base
extend FriendlyId
friendly_id :user_name , use: :slugged
def user_name
user.name
end
But it is giving error
NoMethodError at /
undefined method `name' for nil:NilClass
now After submitting user form.
Please suggest possible solution with explanation.
My User.rb looks like
require 'open-uri'
class User < ActiveRecord::Base
paginates_per 10
validates :name , presence: true, length: { maximum: 200 }
scope :by_name, ->(name) do
joins(:profile).where('lower(name) LIKE ?', "%#{name.downcase}%")
end
delegate :age, :city, :region, :country, to: :profile
has_one :profile, dependent: :destroy
accepts_nested_attributes_for :profile
def self.new_with_session(params, session)
session_params = { 'profile_attributes' => {} }
provider = session['devise.provider']
if provider && data = session["devise.#{provider}"]
session_params['name'] = data[:name] if data[:name]
session_params['email'] = data[:email] if data[:email]
session_params['profile_attributes'] =
{ avatar: data[:image] } if data[:image]
end
params.deep_merge!(session_params)
super.tap do |user|
if auth = Authorization.find_from_session(session, provider)
user.authorizations << auth
end
end
end
def password_required?
super && registered_manually?
end
def registered_manually?
encrypted_password.present?
end
end
And my profile.rb looks like
class Profile < ActiveRecord::Base
extend FriendlyId
friendly_id user.name, use: :slugged
belongs_to :user
accepts_nested_attributes_for :user
validates :website, allow_blank: true, uri: true
def website=(url_str)
if url_str.present?
url_str = "http://#{url_str}" unless url_str[/^https?/]
write_attribute :website, url_str
end
end
end
I think Problem is here:
Request parameters
{"action"=>"new", "controller"=>"users/registrations"}
Please suggest possible solution and explanation.
And users/registration:
class Users::RegistrationsController < Devise::RegistrationsController
layout 'land'
def create
params[:user][:profile_attributes].delete(:place)
end
protected
def after_sign_up_path_for(resource)
welcome_path
end
end
I am creating user in profile controller
def load_profile
#profile = Profile.friendly.find(params[:id])
if !#profile || #profile.user.blocked_users.include?(current_user)
redirect_to home_path
else
#user = #profile.user
end
end
#Rodrigo helped me find out error that error is due to Friendly_id can't create link with user instance.
There is an error on this line:
friendly_id user.name, use: :slugged
The variable user doesn't exists at Profile class scope. You should use something like this:
friendly_id :user_name, use: :slugged
def user_name
user.name
end
extend FriendlyId
friendly_id u_name, use: :slugged
def u_name
user.name
end
belongs_to :user
Have you defined user? what is user.name?

Unable to change URL path after updating attributes with friendly_id

I've been following the Railscast tutorial on how to implement friendly_id and for some reason my URL's doesn't change after I update my attributes.
Say I registered a user with :fullname 'John Doe' it creates the slug /john-doe successfully. However if I update my name to 'Test user' the url is still /john-doe
My current setup is this:
users_controller
def show
#user = User.friendly.find(params[:id])
end
model - user.rb
extend FriendlyId
friendly_id :fullname, use: [:slugged, :history]
I've also migrated
class AddSlugsToUsers < ActiveRecord::Migration
def change
add_column :users, :slug, :string
add_index :users, :slug, unique: true
end
end
so that is working. Also installed
rails generate friendly_id
and done:
User.find_each(&:save)
in rails c
What am I doing wrong?
friendly_id
It's a common issue with friendly_id - it defaults to setting the slug only if the slug attribute is blank:
Documentation
As of FriendlyId 5.0, slugs are only generated when the slug field is
nil. If you want a slug to be regenerated,set the slug field to nil:
restaurant.friendly_id # joes-diner
restaurant.name = "The Plaza Diner"
restaurant.save!
restaurant.friendly_id # joes-diner
restaurant.slug = nil
restaurant.save!
restaurant.friendly_id # the-plaza-diner
You can also override the #should_generate_new_friendly_id? method, which lets you control exactly when new friendly ids are set:
class Post < ActiveRecord::Base
extend FriendlyId
friendly_id :title, :use => :slugged
def should_generate_new_friendly_id?
title_changed?
end
end
If you want to extend the default behavior but, adding your own conditions, don't forget to invoke super from your implementation:
class Category < ActiveRecord::Base
extend FriendlyId
friendly_id :name, :use => :slugged
def should_generate_new_friendly_id?
name_changed? || super
end
end
For you, I'd recommend:
#app/models/user.rb
Class User < ActiveRecord::Base
...
def should_generate_new_friendly_id?
name_changed? || super
end
end

FriendlyId triggers BEFORE model validations. How do I get around this?

Ideally i want urls that look like:
/users/john-s
/users/foo-b
/users/brad-p
I have a user model that looks like this:
class User < ActiveRecord::Base
extend FriendlyId
friendly_id :name, :use => :slugged
validates :first_name, :presence => true
validates :last_name, :presence => true
# "John Smith" becomes "John S."
def name
"#{self.first_name.capitalize} #{self.last_name[0].capitalize}."
end
end
The bad behavior is best explained with this console output:
[15] pry(main)> User.new(first_name: nil, last_name: nil).save!
(0.2ms) BEGIN
(0.1ms) ROLLBACK
NoMethodError: undefined method `capitalize' for nil:NilClass
The Issue (finally! :) )
It appears what happens is that FriendlyId is called BEFORE my validations for first_name and last_name are triggered. This results in the name method pooping when capitalize is called on a nil value.
What can I do so that my validations are triggered before FriendlyId is called? And actually taking it a bit further... Why is FriendlyId involved at all prior to any validity being established?
Thank you!!
It is invoked because the slug is generated prior to validation on save:
https://github.com/FriendlyId/friendly_id/issues/280
I am not quite sure what it would take to monkeypatch it.
The way I wound up fixing mine was like this:
def name
"#{self.first_name.capitalize} #{self.last_name[0].capitalize}." if first_name.present? && last_name[0].present?
end
I think the way to go is to set the user name in a before_validation on create that has is prepended to the friendly_id's own before_validation callback of setting the slug:
class User < ActiveRecord::Base
extend FriendlyId
friendly_id :name, :use => :slugged
# Make sure to prepend it so that it runs before Friendly_id's own callback
# http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html
before_validation :set_name, on: :create, prepend: true
validates :first_name, :presence => true
validates :last_name, :presence => true
# To control when new slugs should be generated
def should_generate_new_friendly_id?
new_record? || first_name_changed? || last_name_changed?
end
private
def set_name
"#{self.first_name.capitalize} #{self.last_name[0].capitalize}."
end
end
Hope this helps!

Resources