Rails clone or hash? - ruby-on-rails

Hey,
i need to use current_user model in order to perform some calculations inside a function. Inside the function i need to do something like current_user.name = 'whatever', thus changing the current value of name.
However, i want that change to be local, only done inside that function. Since Rails uses objects though, it's a problem. So i'm thinking, what is the best thing to do ?
Maybe clone current_user to a new object and use that inside the function ? This seems expensive.
Or maybe creating a hash out of a model ? And if i do that, the actual model will not be changed ?
EDIT: It seems that hash works, but there is no type associated with it, so if i do something like :
#attacker = current_user.attributes
then, to use it, i have to specify to_s like (else i get a nil error for some reason):
#attacker[:name].to_s = 'whatever'

Parameters?
def my_f(name)
new_name = "lorem" + name
return new_name
end
Somewhere in your controller:
loremized_name = my_f(current_user.name)

If you need all of the logic in your model, the easiest way would be to simply clone it:
def local_function
user = current_user.clone
# Perform some calculations on user here
end

Related

How to use an argument's value to build a `.collection` method for an associated model?

I'm not sure what the correct terminology is for my question, but is it possible to use an argument's value to "build" its appropriate .collection method?
For instance, a Submission has_many images, tags, and documents. And depending on what the user is interacting with, I'd like to create the appropriate association.
Initially, I had it set up for only the tags...
def add_to_submission(task, user, tag)
sub = Submission.find_or_create_by(task: task, user: user)
sub.tags << tag
end
But is there a way I can generalize it further so that the third argument can by more dynamic? So rather than only accept a tag, it could be used like
add_to_submission(#task, #current_user, #new_image)
Something along the lines of...
def add_to_submission(task, user, associated_item)
sub = Submission.find_or_create_by(task: task, user: user)
items = associated_item.pluralize
sub.items << associated_item
end
For dynamic calling of methods .send can be used. You can pass a symbol of the method name. You should be able to do something like this, but I would make sure to have good unit tests for your method.
def add_to_submission(task, user, associated_item)
submission = Submission.find_or_create_by(task: task, user: user)
children = associated_item.pluralize.to_sym
submission.send(children) << associated_item
end

How to get the parent objects class name from an attribute

Is it possible to return the parent object of a given attribute?
Example
a = User.birthdate
a.parent_object ... should return the user record that is the parent of the birthdate attribute
A better example?
Helper
def item_grade(subject, obj)
obj.scale.grades.find(subject.grade_id).name # would return something like "Pass", "Fail", "Good Job"
end
In the view
item_grade(#course.subject, #course)
This approach requires two options to be passed to the helper. It seems I should be able to pass #course.subject and then get the parent object from that
Helper
def item_grade(subject)
a = subject.parent_object.scale
a.grades.find(subject.grade_id).name
end
View
item_grade(#course.subject)
This approach requires two options to be passed to the helper.
You can remove some duplication by doing this, for example.
def item_grade(obj, property)
obj.scale.grades.find(obj.send(property).grade_id).name
end
item_grade(#course, :subject)
Now you don't have to repeat #course in the call.
Having to pass two parameters is much less harmful than any sort of hackery you can come up with (thanks #muistooshort). There's no built-in way to do this.

Cannot assign ActiveRecord Attributes in Model

I am trying to assign values of ActiveRecord attributes within my models, but for whatever reason, I can't set them.
For instance, I have an AccountModel and this has an Attribute name
If I set it from the controller or the console (like user.name = "John"), everything works fine.
But, if I try to set it from within the model, like
def set_name(new_name)
name = new_name
end
then it doesn't work. On the other hand, retrieving the name, like
def get_name
name
end
works just fine. Am I missing something?!
I am using Ruby 2.0.0-p247 and Rails 4.0.0; Please note, that this examples aren't real world examples, I just tried to keep them simple to clarify my problem.
Best regards,
Mandi
Try:
def set_name(new_name)
self.name = new_name
end
You need to use the self keyword to refer to your instance attributes on assignment. Otherwise ruby will assign your new name to a local variable called name.
You might want to save your changes after
user = User.new
user.set_name('foo')
user.save
Take a look at the example here, there is one similar to your question at the end ;)
Your code looks fine, but are you saving the changes? Try adding a save call:
def set_name(new_name)
self.name = new_name
self.save
end
You don't need to do self.save, just call save inside your model. You only use self.attribute when you need to assign.
Try
def set_name(new_name)
self.name = new_name
self.save!
end
Then call the instance method from controller simply
user.set_name('foo')

Where do I put the Current user query so as to not repeat per controller?

I have a standard query that gets the current user object:
#user = User.find_by_email(session[:email])
but I'm putting it as the first line in every single controller action which is obviously not the best way to do this. What is the best way to refactor this?
Do I put this as a method in the Application controller (and if so, can you just show me a quick example)?
Do I put the entire #user object into the session (has about 50 columns and some sensitive ones like is_admin)?
Or is there another way to remove this kind of redundancy?
I suggest making it into a helper placed in the ApplicationHelper module
def current_user
return nil if #user === false
#This ensures that the find method is only called once
#user = #user || User.find_by_email(session[:email]) || false
end
I prefer the above usage instead of the standard #user ||= User.find... because it prevents repetitive queries if the user record isn't found the first time. You could also just bang the find method: find_by_email! to make it throw an exception when the user can't be found
You could specify a before_filter, which is automatically called at the beginning of every controller action. Read up on it to see how to use it.

How to setup default attributes in a ruby model

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

Resources