I have first_name and last_name fields currently represented in my model in rails. I'd like to add another field to my database called full_name and was wondering what the best way to do this seamlessly would be. Mind you I also have a production server going and would like to make it so that I not only add a column in my migration, but also can populate the new field with the existing data.
EDIT:
Here's my controller
def followers
#title = "Followers"
#user = User.find(params[:id])
#users = #user.followers.paginate(page: params[:page])
#followers = #user.followers.find(:all, :select => 'users.id, users.first_name, users.last_name', :conditions => ["users.first_name like ?","%" + params[:q] + "%"])
respond_to do |format|
format.html {render 'show_follow'}
format.json {render :json => #followers}
end
end
I want to be able to 1. select: 'users.id, users.full_name and 2. :condition =>["users.full_name like ?", ...] and the only way I can think to do this is to modify the model. I also only want to return the properties id and full_name in the json object.
You will probably be better off just defining a full_name method in your model:
def full_name
([first_name, last_name] - ['']).compact.join(' ')
end
You can search by full name with something like:
def self.find_all_by_name_containing(text)
self.where("LOWER(first_name || ' ' || last_name) LIKE ?", "%#{text.downcase}%")
end
Then define your own #to_json
If you really want to go ahead with adding a full_name field to your User model, then I suggest the following steps.
1.Generate a rails migration adding the field full_name to users table.
rails g migration add_full_name_to_users full_name:string
2.Add the following before_save callback to your User model.
Class User < ActiveRecord::Base
...
before_save :set_full_name
private
def set_full_name
self.full_name = "#{self.first_name} #{self.last_name}".strip
end
end
3.Commit, push code to production, run migration on production.
4.Run a save or save! method on all your users in rails console.
$> User.find_each(:batch_size => 1000){ |user| user.save }
I guess, that should take care of updating your existing users, plus the before_save call will take care of future additions/updates.
Generate migration
rails g migration add_full_name_to_users
Inside migration:
def up
add_column :users, :full_name, :string
execute <<-SQL
UPDATE users SET full_name = first_name||' '||last_name WHERE full_name IS NULL;
SQL
end
It will create full_name column and populate it with default data. You can also do something like this in ruby but it will be much faster in SQL.
Related
I have a rails modle like this
class Article < ActiveRecord::Base
has_many :tags
def all_tags=(keys)
self.tags = keys.split(',').map do |key|
Tag.where(article_id: id, key: key.strip).first_or_create!
end
end
def all_tags
tags.map(&:key).join(', ')
end
end
Basically what I want to do is to allow user set tags on it, it would look like this in controller
def create
#article = article(article_params)
if # article.persisted?
redirect_to article s_path
else
render 'new'
end
end
However, as in def all_tags=(keys), article.id is not present yet. So I got error like this
PG::NotNullViolation: ERROR: null value in column "article_id" violates not-null constraint
Here is the question, how to ensure article is persisted before all_tags got update?
When you use Model.where(conditions).first_or_create!, Active Record tries to insert into database a new record for Model if can't found one.
In your case, since you are setting the attributes before save the model, then the create launch an exception.
To fix the issue just change first_or_create with first_or_initialize
Yes you can do validation something like
validates :your_method, on: :update
def your_method
{with logic not be null}
end
class Contact < ActiveRecord::Base
attr_accessor :email
def initialize(data)
data.each { |k, v| send("#{k}=", v) }
end
end
In rails console
Contact.create!({"email"=>"foo#gmail.com"})
The record saved to the database has email as nil
Update:
The data is being passed in is JSON. I am getting all the data from the JSON and trying to save that into the database.
Did you try:
Contact.create!(email: "foo#gmail.com")
The email as a :symbol and no curly brackets?
Also, why are you initializing in your model?
With Mohamed El Mahallaway, I think your code setup could be improved (to negate initializing your model). I think you'll be better using the strong_params functionality of Rails:
#app/controllers/contacts_controller.rb
def new
#contact = Contact.new
end
def create
#contact = Contact.new(contact_params)
#contact.email = "foo#gmail.com"
#contact.save
end
private
def contact_params
params.require(:contact).permit(:email, :other, :params)
end
I may have miscalculated your competency with Rails, but this is the "Rails" way to save the correct data to your model :) You may to have a before_save method in your model to use the email attribute, considering it's a virtual attribute
UPDATE: I've found a way that works, though it's not very flexible, by taking the params hash and comparing its keys to my model's column names. I then take that array of names and map it to a hash, that I then use in my ActiveRecord query. There must be a better way?
def index
if params
hash = {}
attributes = User.column_names & params.keys
attributes.each do |attribute|
hash.merge!(attribute.to_sym => params[attribute.to_sym])
end
#users = User.where(hash)
else
#users = User.all
end
respond_with #users
end
BACKGROUND: I've hooked up an Ember app to a Rails JSON API and have figured out how to query the database using Ember-Data. Below is an example in Coffeescript:
App.UsersRoute = Ember.Route.extend
model: ->
# Step 1: Query database for all users
#store.find('user')
# Step 2: Filter results (keep male users named "Steve")
#store.filter 'user', (user)->
user.get('name') == "Steve" && user.get('gender') == "Male"
OBJECTIVE: I'm wondering if this is the best way to go about this? Wouldn't querying for all users get increasingly difficult as the number of users increases?
I'm thinking a good alternative would be to include the query as parameters on my initial query, like so:
#store.find 'user', {name: "Steve", gender: "Male"}
# Sends JSON request to /users.json?name=Steve&gender=Male
If this is a better approach, I am stumped as to how to make Rails take these two parameters and query the database for them. Below is my Rails controller that responds to the above request:
class Api::V1::UsersController < ApplicationController
respond_to :json
def index
respond_with User.all
end
end
In order to accommodate the above request, I'd have to do something like this:
def index
respond_with User.where(name: params[:name], gender: params[:gender])
end
But this would not accommodate any additional queries, or queries that don't have both of these params set. Which of these two approaches is best?
You can try doing like this, it allows you to customize your where and other clauses depending upon input params:-
def index
#user = User
if params[:name].present? && params[:gender].present?
#user = #user.where(name: params[:name], gender: params[:gender])
end
#user = #user.all
.....
end
In my Rails 3.1.1 project I have an ActiveModel that talks to API (ripped from Paul Dix's book, shortened for readability):
class Job
include ActiveModel::Validations
include ActiveModel::Serializers::JSON
ATTRIBUTES = [ :id,
:title,
:description,
:company_id ]
attr_accessor *ATTRIBUTES
validates_presence_of :title, :description
validates_numericality_of :company_id, :id
def initialize(attributes = {})
self.attributes = attributes
end
def attributes
ATTRIBUTES.inject(
ActiveSupport::HashWithIndifferentAccess.new
) do |result, key|
result[key] = read_attribute_for_validation(key)
result
end
end
def attributes=(attrs)
attrs.each_pair {|k, v| send("#{k}=", v)}
end
def read_attribute_for_validation(key)
send(key)
end
# More method definitions...
end
I instantiate #job in my controller, new action (company_id is a segnment key in the route: /companies/:company_id/jobs/new) like this:
#job = Job.new(company_id: params[:company_id])
Then, using CanCan, I check user's permissions to create to create a job. Basically, CanCan checks if current_user's company_id attribute matches job's company_id. This check fails because #job.company_id is returned as String.
Certainly, I can use params[:company_id].to_i while instantiating the object, but this seems like a workaround that I would have to repeat later.
Question: is there a way to make my Job ActiveModel more "type-aware" and make it return int for #job.company_id call?
I googled around, checked activemodel source code, but doesn't seem to find an answer. Any help is greatly appreciated.
Update
I was thinking more of something like schema block for ActiveModel, just like the one in ActiveResource.
attr_accessor *ATTRIBUTES
create a method like this:
def company_id
#company_id
end
You can just override that with
def company_id
#company_id.to_i
end
Answering my own question....
mosch's answer suggested to override the getter for company_id in my ActiveModel. However, I would have to repeat this for all of _id attributes in the model. Therefore, I decided to cast all of the '_id' attributes to integers while initializing the object. As follows:
def attributes=(attrs)
attrs.each_pair do |k, v|
if "#{k}".include? "_id"
send("#{k}=", v.to_i)
else
send("#{k}=", v)
end
end
end
I'm assuming your Company has_many => :jobs? If so, you could try
def new
#company = Company.find(params[:company_id])
#job = #company.jobs.new
end
When user's create a post I'd like to set the user_id attribute first. I'm trying to do this using alias_method_chain on the arrtibutes method. But I'm not sure if this is right as the problem I thought this would fix is still occurring. Is this correct?
Edit:
When my users create a post they assign 'artist(s)' to belong to each post, using a virtual attribute called 'artist_tokens'. I store the relationships in an artist model and a joined table of artist_ids and post_ids called artisanships.
I'd like to to also store the user_id of whomever created the artist that belongs to their post (and I want it inside the artist model itself), so I have a user_id column on the artist model.
The problem is when I create the artist for each post and try to insert the user_id of the post creator, the user_id keeps showing as NULL. Which is highly likely because the post's user_id attribute hasn't been set yet.
I figured to get around this I needed to set the user_id attribute of the post first, then let the rest of the attributes be set as they normally are. This is where I found alias_method_chain.
post.rb
attr_reader :artist_tokens
def artist_tokens=(ids)
ids.gsub!(/CREATE_(.+?)_END/) do
Artist.create!(:name => $1, :user_id => self.user_id).id
end
self.artist_ids = ids.split(",")
end
def attributes_with_user_id_first=(attributes = {})
if attributes.include?(:user_id)
self.user_id = attributes.delete(:user_id)
end
self.attributes_without_user_id_first = attributes
end
alias_method_chain :attributes=, :user_id_first
EDIT:
class ArtistsController < ApplicationController
def index
#artists = Artist.where("name like ?", "%#{params[:q]}%")
results = #artists.map(&:attributes)
results << {:name => "Add: #{params[:q]}", :id => "CREATE_#{params[:q]}_END"}
respond_to do |format|
format.html
format.json { render :json => results }
end
end
In your controller, why not just do this:
def create
#post = Post.new :user_id => params[:post][:user_id]
#post.update_attributes params[:post]
...
end
But it seems to me that it would be much better to create the artist records after you've done validation on the post rather than when you first assign the attribute.
EDIT
I would change this to a callback like this:
class Post < ActiveRecord::Base
attr_accessor :author_tokens
def artist_tokens=(tokens)
#artist_tokens = tokens.split(',')
end
after_save :create_artists
def create_artists
#artist_tokens.each do |token|
...
end
end
end