How to setup default attributes in a ruby model - ruby-on-rails

I have a model User and when I create one, I want to pragmatically setup some API keys and what not, specifically:
#user.apikey = Digest::MD5.hexdigest(BCrypt::Password.create("jibberish").to_s)
I want to be able to run User.create!(:email=>"something#test.com") and have it create a user with a randomly generated API key, and secret.
I currently am doing this in the controller, but when I tried to add a default user to the seeds.rb file, I am getting an SQL error (saying my apikey is null).
I tried overriding the save definition, but that seemed to cause problems when I updated the model, because it would override the values. I tried overriding the initialize definition, but that is returning a nil:NilClass and breaking things.
Is there a better way to do this?

use callbacks and ||= ( = unless object is not nil ) :)
class User < ActiveRecord::Base
before_create :add_apikey #or before_save
private
def add_apikey
self.apikey ||= Digest::MD5.hexdigest(BCrypt::Password.create(self.password).to_s)
end
end
but you should definitely take a look at devise, authlogic or clearance gems

What if, in your save definition, you check if the apikey is nil, and if so, you set it?

Have a look at ActiveRecord::Callbacks & in particular before_validation.

class User
def self.create_user_with_digest(:options = { })
self.create(:options)
self.apikey = Digest::MD5.hexdigest(BCrypt::Password.create("jibberish").to_s)
self.save
return self
end
end
Then you can call User.create_user_with_digest(:name => "bob") and you'll get a digest created automatically and assigned to the user, You probably want to generate the api key with another library than MD5 such as SHA256 you should also probably put some user enterable field, a continuously increasing number (such as the current date-time) and a salt as well.
Hope this helps

I believe this works... just put the method in your model.
def apikey=(value)
self[:apikey] = Digest::MD5.hexdigest(BCrypt::Password.create("jibberish").to_s)
end

Related

Update attribute in associated model in Rails when parent is updated

I have two models User and Assignment. Whenever User is updated I want to update the url attribute in Assignment.
How do I do this?
class User
has_many :assignments
...
end
class Assignment
belongs_to :user
before_save :set_url
def set_sandbox_url
language = 'www'
project = 'example'
base_url = "https://#{language}.#{project}.org/"
sandbox_url = "#{base_url}/User:#{user.username}/#{article_title}"
end
I agree with Tamer Shlash that there is not really a benefit in storing this URL in the database because it can be easily generated each time you need it.
But apart from that, I would like to answer your question. Your callback to regenerate the URL doesn't work for various reasons. First, you want to update the URL when the user changes therefore the user needs to have a callback defined. Second, the naming is not correct. The callback as it is currently written would try to run a set_url method but the method is actually called set_sandbox_url. And third, sandbox_url = will just assign the new URL to a local variable sandbox_url but it would not update the instance variable #sandbox_url.
I would do something like this:
# in app/models/user.rb
after_save :update_assignment_url
private
def update_assignment_url
assignments.each(&:update_url) if username_previously_changed?
end
# in app/models/assignments.rb
def update_url
language = 'www'
project = 'example'
base_url = "https://#{language}.#{project}.org/"
sandbox_url = "#{base_url}/User:#{user.username}/#{article_title}"
update!(sandbox_url: sandbox_url)
end
Note: because you build the URL by simply concatenating strings I suggest making sure that these strings (especially values provided by the user like username and article_title) only include characters that are valid in an URL (for example by using String#parameterize).
You might want to read about Dirty Attributes too which provided the used username_previously_changed? method.

Initialize an ActiveRecord instance using a parameter not on the table

The User model has an id column that is used all throughout your schema, but you'd like to create other models using the username rather than the id:
User.create!(username: "phillip")
User.create!(username: "joe")
Message.create!(from_username: "phillip", to_username: "joe", message: "hello")
Here, 'from_username' and 'to_username' don't actually exist on the table. Rather, there is a 'from_id' and 'to_id'. You can add 'from_username' and 'to_username' as methods to the model, but then you can't create a new Message using them.
What is the recommended way to add these 'virtual attributes' to a model?
Most idiomatic way
You could define a custom create function, like this:
class Message < ActiveRecord::Base
def self.create_from_usernames(from_username, to_username)
from_user = User.find_by_username(from_username)
to_user = User.find_by_username(to_username)
self.create(from_user: from_user, to_user: to_user)
end
end
Making call-site code handle this itself
This is still somewhat idiomatic, but not very DRY.
from_user = User.find_by_username("phillip")
to_user = User.find_by_username("joe")
Message.create!(from_user: from_user, to_user: to_user)
Using create directly
This is not really recommended, but you could do something like this:
class Message < ActiveRecord::Base
attr_accessor :from_username, :to_username
before_create :find_users
private
def find_users
self.from_user = User.find_by_username(from_username)
self.to_user = User.find_by_username(to_username)
end
end
This leads to somewhat "magical" behavior though, and there should be more error checking / handling if you're going to do it.
I think the normal way to do that would be to include these attributes into your Message model. So instead of saving the user_id you can also save the user_name. Otherwise, just work around it and get the username by the user_id. May be I did not fully understand your question, though..

How can I automatically create an adress parameter upon sign up with Devise (Ruby On Rails)

I'm currently trying to automatically create an user_address (which will be a randomly generated hash, which is for now hardcoded) string upon sign-up with the Ruby on Rails devise Gem. I have added the user_address to the list of allowed parameters I am currently trying to add this logic to the registrations_controller.rb by adding the following method :
def create
super do
current_user.user_address = '1F1tAaz5x1HUXrCNLbtMDqcw6o5GNn4xqX'
end
end
I suppose this is because current_user must not be defined before the actual POST (create user) has been processed but I am not sure about the way to do this.
Any help would be greatly appreciated, thanks in advance
If i understand you correctly (I think I do) you could move away from trying to do this in the create action in the controller and instead use a model callback on the User model.. that way its automatically created when a user registers.
example:
class User < ApplicationRecord
before_create :assign_user_address
validates_uniqueness_of :user_address
def assign_user_address
begin
self.user_address = SecureRandom.hex
other_user = User.find_by(user_address: self.user_address)
end while other_user
end
end
the before_create will generate the user_address and assign it to the user that is registering, while the validates_uniqueness_of ensures that you will never have a duplicate user address all in one fell swoop (although with a random string the chances of duplicating are slim.. but better to be safe than sorry.. I find this method super easy and keeps your controller clean and un cluttered.
Let me know if this wasn't what you were looking for..

Having two different sort of validate, from user or app itself in rails

My program is trying to create some groups automatically, with a prefix of 'automated_group', it won't show up when loading all groups, can't be edited, and some more stuff. But I have to limit users from doing it.
But if I make a validate function, it won't let my app do it and group.save returns false. Even when updating other attributes, it won't let me save it, cause the name won't validate.
Is there any other way? Sometimes use validation, or maybe check who's changing the value?
Thanks in advance
You can use a permission system like cancan (https://github.com/ryanb/cancan). Then you can define someting like this:
can :manage, Group, automated_group: false
I've found the half of the answer in skip certain validation method in Model
attr_accessor :automated_creation
validate :check_automated_name, unless: :automated_creation
def check_automated_name
#...
end
and in my controller:
def get_automated_group
name = "automated_group_#{product.id}"
group = Group.find(name: name).first
group Group.new(automated_creation: true) unless group.blank?
returrn group
end
When updating:
I'll check in check_automated_name function that it any change on name has happened with:
group.name_changed?
so any thing else can be updated except 'name', which the only way of creation is from rails itself.

ActiveRecord::Base Class Not Mutable?

I have a class I've extended from ActiveRecord::Base...
class Profile < ActiveRecord::Base
and I collect the records from it like so...
records = #profile.all
which works fine, but it doesn't seem that I can successfully Update the attributes. I don't want to save them back to the database, just modify them before I export them as JSON. My question is, why can't I update these? I'm doing the following (converting date formats before exporting):
records.collect! { |record|
unless record.term_start_date.nil?
record.term_start_date = Date.parse(record.term_start_date.to_s).strftime('%Y,%m,%d')
end
unless record.term_end_date.nil?
record.term_end_date = Date.parse(record.term_end_date.to_s).strftime('%Y,%m,%d')
end
record
}
At first I had just been doing this in a do each loop, but tried collect! to see if it would fix things, but no difference. What am I missing?
P.S. - I tried this in irb on one record and got the same results.
I suggest a different way to solve the problem, that keeps the logic encapsulated in the class itself.
Override the as_json instance method in your Profile class.
def as_json(options={})
attrs = super(options)
unless attrs['term_start_date'].nil?
attrs['term_start_date'] = Date.parse(attrs['term_start_date'].to_s).strftime('%Y,%m,%d')
end
unless attrs['term_end_date'].nil?
attrs['term_end_date'] = Date.parse(attrs['term_end_date'].to_s).strftime('%Y,%m,%d')
end
attrs
end
Now when you render the records to json, they'll automatically use this logic to generate the intermediate hash. You also don't run the risk of accidentally saving the formatted dates to the database.
You can also set up your own custom option name in the case that you don't want the formatting logic.
This blog post explains in more detail.
Try to add record.save! before record.
Actually, by using collect!, you just modifying records array, but to save modified record to database you should use save or save! (which raises exception if saving failed) on every record.

Resources