Was debating with a colleague of mine tonight on methods vs attributes in Rails. I swear I have seen this before unless I am crazy. Here is an example:
We have a user model and we want to be certain we have a first name and a last name prior to saving the record. I hate callbacks, but for this example, let's say it is in a before_save callback.
class User
before_save :set_first_last
def set_first_last
first_name if self.first_name.blank?
last_name if self.last_name.blank?
end
def first_name
self.first_name = self.name.split(" ").first
end
def last_name
self.last_name = self.name.split(" ").last
end
end
So, I am curious if the method "set_first_last" name is called and it sees that the attribute first_name on user is blank it will call the method "first_name", correct? The attribute first_name on user is different than a method called "first_name." So an attribute on the user object called "first_name" would be different than a method on the user class called "first_name" in Rails, correct? This would be like a class level method vs an instance method? Just want to be sure I am correct and that I am explaining this correctly.
thanks
--Mike
I think you are misreading this bit of the code:
def set_first_last
first_name if self.first_name.blank?
last_name if self.last_name.blank?
end
This code calls the first_name method, and if it is blank, calls the first_name method again (in other words the if parts of this method are pointless)
The way this code is constructed, the value of the first_name and last_name attributes will never be used (other than if you were to use them in your queries) - any time you do user.first_name it will reconstruct it from name. A more common pattern would be to have the methods that set the names called something like ensure_first_name (Basically anything that doesn't class with the getter)
Instance variables (eg #foo) are distinct from methods of the same name, although it is of course common for foo and foo= to be the methods for getting/setting an instance variable of that name. Perhaps you were thinking of that? In an event the attributes of an Activerecord model aren't stored in individual instance variables.
To answer the part I think I understand: yes, an attribute on a model is quite different than a method.
Take, for example, a User model, a Users controller, and one of its corresponding views. Let's pretend the User model itself contains first_name, last_name, and email properties:
class User < ActiveRecord::Base
#Ensure presence of the following before saving model
validates_presence_of :first_name, :last_name, :email
#Verify unique email
validates_uniqueness_of :email
#Here's our lone User method...poor guy
def admin?
self.company_members.where('? = ANY (company_members.roles)', Role::ADMIN).any?
end
end
When you get around to working with an instance of that model in your controller, it'll look something like this:
class UsersController < ApplicationController
def index
#Let's just grab a random user
#user = User.find(1)
end
end
Finally, in your view, your properties (attributes) can be accessed in the following way:
####views/users/index.html.haml####
.random-class
= "You're logged in as #{#user.first_name}!"
But we can also call User methods in the view as well. Continuing off our last example, we could add something like this:
####views/users/index.html.haml####
.random-class
= "You're logged in as #{#user.first_name}!"
- if #user.admin?
= 'Oh my gosh, our freaking User method worked!'
So in short, you're correct in that properties and methods are very different, although they look similar at times.
Lastly, it's worth pointing out that instance methods are just that: methods called on instances of a class, whereas class methods are called on an actual model object, and not a singular instance of that model object. Here's a great link to learn more about the differences.
Hope this helps!
Yes, what you explained is correct.
Your piece of coding is going to work and assign the first_name & last_name with the splitted form.
further you can write your code in this way as well (assuming your full name consists of 2 words separated by space)
#user.rb
class user
before_save :assign_individual_names
private
def assign_individual_names
first_name, last_name = name.split(' ')
end
end
Related
Say I have a Users table with a first_name field. Automatically I have a User model.
What I wan't is to know if it is possible to make a method also named first_name in the User model work?
I might do some modification inside the method. Say the model and the method would look like this:
class User < ApplicationRecord
def first_name
# I know there are many ways to do this, this is just an example.
"Mr. #{first_name}"
end
end
I'm getting SystemStackError: stack level too deep in the console. Just wanna know if this is possible or if this can work.
You can use read_attribute method
class User < ApplicationRecord
def first_name
# I know there are many ways to do this, this is just an example.
"Mr. #{read_attribute(:first_name)}"
end
end
I added a new field in devise called firstname, and I want it to be capitalized by devise during registration.
I first ran:
rails generate migration add_username_to_users firstname:string
then
rake db:migrate
After that I added firstname to the configure_permitted_parameters in the application_controller.rb and updated the views. I basically used this but stripped out some unnecessary stuff.
I dont know where I should put the code for capitalizing the firstname and lastname (as well as some other validating). Any guidance would be appreciated. Thanks.
I think you should put capitalization of first and last names in your User model. Every time a user is saved, you can capitalize the first and last name. In addition, all validation (or attribute pre-processing/sanitization) can be done at the model level as well.
class User < ActiveRecord::Base
before_save :capitalize_names
def capitalize_names
self.firstname = firstname.camelcase
self.lastname = lastname.camelcase
end
end
before_create
Joe Kennedy's answer is correct - you should use the before_create ActiveRecord callback
The difference here is that Devise doesn't do anything with your actual data modelling - it basically just creates a series of controllers to handle the user registration & login processes
--
If you want to ensure certain attributes of your User model are saved in a particular style, you'll be best setting it in the model itself:
#app/models/user.rb
Class User < ActiveRecord::Base
before_create :set_firstname
private
def set_firstname
self.firstname.titeize
end
end
This should allow you to set the attribute to have the first letters of each word capitalized
--
System
An alternative would be to look at your system
Why are you insisting the data be stored this way? It seems very inefficient to save all your data in the same for the sake of styling.
I would use the CSS text-transform function to do this:
#app/assets/stylesheets/application.css
.first_name { text-transform: capitalize; }
#app/views/users/show.html.erb
<%= content_tag :span, #user.firstname, class: "first_name" %>
Best Solution Ever:
class Role < ApplicationRecord
before_save :capitalize_names
def capitalize_names
self.name.titlecase
end
end
Output will be:
'super admin'.titlecase
Super Admin
This should probably go in the User Controller (or whichever controller inherits from the Devise Controller and creates the new user). In the create method, before you save the user to the database, add whatever attributes you want to it (i.e. capitalizing the first letter) and then save it.
def create
User.create(email: params[:email], first_name: params[:first_name].capitalize)
end
Although I'd suggest you just output the capitalize in your views and not when saving.
I have model User and model Recruiter. Currently, these are two separate tables, but I want to make them one.
Current:
User: id, username, password, name
Recruiter: id, user_id
Ideal:
User: id, username, password, role (recruiter, admin)
I understand the basics of STI. What I'm wondering is, when I perform methods on the new Recruiter controller (that inherits from User) how do I make sure all my methods are calling on users that are only a recruiter? Thus, queries along the lines of... SELECT * FROM users WHERE role = 'recruiter' for everything.
That is something rails takes care of for you, out of the box. You do not have to manually query on a particular type of user, just query on the right model.
I must also mention that by default rails assumes that your sti_column is called type, but can be overridden to role easily.
Let's admit you have your 2 classes:
class User < ActiveRecord::Base
end
class Recruiter < User
end
Rails will automagically add a type column in the users table so that in your controller, if you do something like this:
class RecruitersController < ApplicationController
def index
#recruiters = Recruiter.all
end
end
Rails will automatically fetch the records with type = 'Recruiter' and you don't even have to set this manually. If you do:
Recruiter.new(name: 'John').save
A new User will be created in database with the field type set to 'Recruiter'.
you would define your models something like this:
class User < ActiveRecord::Base
...
end
class Recruiter < User
...
def initialize
# ... special initialization for recruiters / you could put it here
super
end
...
end
and to create a new recruiter, you would do this:
Recruiter.create(:name => "John Smith")
and because of the type attribute in the STI user table (set to 'Recruiter'), the record will be for a recruiter.
You could put the special initialization for the STI models either in the model's initializer, or in a before filter with a if-cascade checking the type.
An initializer is probably much cleaner.
Have you tried has_one association?
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_one
I'd like to generate unique, anonymous usernames when creating an instance of my User model. Ideally, it would be set by default if I don't specify any username.
So for example when I do User.create!() I'd like to have the username column set to 'anonymous123', where 123 is a unique number taken from a sequence (the value of the id column for that instance would be ok).
Combining the examples from a couple of the answers would do it
class User < ActiveRecord::Base
before_create :set_anonymous_username
def set_anonymous_username
username = "anonymous#{User.last.id + 1}" if username.nil?
end
end
I have used before_create here because I would expect you only to have to set an anonymous username before you create the User for the first time. The before_save will be called every time the model is saved and could be unnecessary overhead, but either will work
Use a before_save filter to set the username before the model is saved, e.g.
class User < ActiveRecord::Base
before_save :set_anonymous_username
def set_anonymous_username
username = "anonymous" + User.find_by_sql("SELECT max(id) FROM users").maxid.to_s if username.nil?
end
end
Something like
class User < ActiveRecord::Base
def initialize
super
self.username = "anonymous#{User.last.id + 1}"
end
end
Will do it when you create a new instance of User, even before it's saved - in case you want to use it in a view pre-creation. You probably want to combine it with something before_save as mentioned above to get the definitive last id in the database at the time of record creation.
I have a user model which has multiple addresses. Now for my application in rails, address is not mandatory. So, if someone wants to create a user and enter the address after the user has been created, my application should allow that. My problem is, for Address model I have validations for Address Line 1, City and Postal Code. These fields cannot be blank. When, editing a user, the following code fails:
user.addresses << Address.new
Rails tries to create a new Address and fires an Insert command. This is going to fail because of the validations that is required in the model. The above code doesn't fail if the user is not present in the database. One solution to this problem is to create a separate form_for binding for the edit partial for user. I don't want to do that solution. Is there any solution that allows me to bind an empty Address object for an already existing User object in the database ?
Why attempt to add an empty Address object to the user.addresses collection? I think you could simply do something like:
user.addresses << Address.new unless (conditions)
I unfortunately don't know what your conditions are here, so it could be something like
user.addresses << Address.new unless params[:address].nil?
...although my guess is that you have a real Address object instead of just passing in a blank Address.new...
user.addresses << Address.new
This code isn't going to work anyway if your Address model requires its fields to be set, because you're not supplying a hash to Address.new
If you want to add the address conditionally, you probably want something like this:
if !params[:address].blank?
user.addresses.create(params[:address])
end
or
user.addresses << Address.new(params[:address]) unless params[:address].blank
If you really want to create an "empty" address object for each user (instead of just having users without addresses), you can change your validations so they only fire if the fields are filled out.
Something like this:
class Address < ActiveRecord::Base
validates_presence_of :address1, :if => :non_empty_address?
# etc
private
def non_empty_address?
!address1.blank? || !address2.blank || !city.blank? # etc
end
end
The restful_authentication plugin uses a similar approach to determine if the user's password is required.