Ruby - how to create dynamic model attributes? - ruby-on-rails

I have an array with model attributes (these model attributes are columns in DB table). I am trying to iterate through this array and automatically create a record which I would like to save to DB table, something like this:
columns.each_with_index do |c, i|
user.c = data[i]
puts user.c
end
user is model.
But if I try the snippet above, I get
undefined method `c=' for #<User:0x007f8164d1bb80>
I've tried also
columns.each_with_index do |c, i|
user."#{c}" = data[i]
puts user."#{c}"
end
But this doesn't work as well.
Data in columns array are taken from form that sends user, so I want to save only data that he send me, but I still cannot figure it out...
I would like to ask you for help... thank you in advance!

user.send("#{c}=".to_sym, data[i])
Also, you can access the attributes as a hash.
user.attributes[c] = data[i]

The best thing would probably be to build a hash and to use update_attributes:
mydata = {}
columns.each_with_index{|c, i| mydata[c] = data[i]}
user.update_attributes(mydata)
this way you retain the protections provided by attr_accessible.

If this is actually in a controller, you can just make use of some basic rails conventions and build the User record like this:
#user = User.new(params[:user])
if #user.save
# do something
else
# render the form again
end
Although you can set the values using send, I agree with #DaveS that you probably want to protect yourself via attr_accessibles. If your planning to use Rails 4, here's a good overview.

Related

Rails.cache.fetch not working when model given as key

I was under the impression that we could give Rails a model (anything responding to to_param or cache_key) to Rails.cache.fetch and it would create a key and cache the response of the block.
I have this code:
class ResultsPresenter
def initialize(project)
#project = project
end
def results
results = Rails.cache.fetch("#{#project}/results") do
sleep 3
a = long_running_query
b = another_long_query
c = a + b
end
end
end
# called
project = Project.find(params[:project_id]_
presenter = ResultsPresenter.new(project)
presenter.results
#project was passed to ResultsPresenter and is an ActiveRecord model. When I specify "#{#project.to_param}/results" or "#{#project.cache_key}/results" everything works just fine. I also checked if the #project was being updated but it's not.
Anyone know why it does not take an ActiveRecord model?
You want your cache key to be an array, probably Rails.cache.fetch([#project, 'results']).
This will give a cache key along the lines of "project/5-20190812000000/results". The format for the model is "model_name/model_id-updated_at" with the rest of the array values joined with /.
If you were to look at the key generated from your example, it would look something like "#<Project:0x007fbceaadbbc90>/results". This is happening because you are baking the value of #project.to_s into the value of the key you are passing into fetch.

save multiple parameters in a loop, rails controller

I've created a form with about 40 fields available to edit, I'm trying to save them to a database using the controller. I currently have this code:
c = Form.find(params[:id])
if c
params.each do |k,v|
c.k = params[:v]
end
Which doesn't work, I get this error: undefined method 'k='
if I was going to write them all out manually it would look like this:
c = Form.find(params[:id])
if c
c.title = params[:title]
c.reference = params[:reference]
....
etc.
Assuming that you're trying to update the attributes on your Form record based on what gets passed into params, try this as a basic outline:
c = Form.find_by_id(params[:id])
if c
params.each do |k, v|
c[k] = v
end
c.save!
end
Your original code's use of params[:v] was probably not doing what you were intending, and you really meant for it to be params[:k] instead. However there's actually no need to look up the value for that key inside the loop like that because you already have the value at hand in v.
Here's a quick rundown on the ways of interacting with ActiveRecord attributes: http://www.davidverhasselt.com/2011/06/28/5-ways-to-set-attributes-in-activerecord/
i dont know what you are trying todo but your code seems to be very odd. Solution is as follow
c.send "#{k}=", params[:v]
What about
c = Form.find(params[:id])
c.update_attributes(params[:form])
Note that I guessed the [:form] part in the second line, it depends on your form. check your html source, and see if your fields are something like this:
<input name="form[field_name]" ...
As you see, name contains an "array like" form. Check your HTML source and adapt (so if its name="foo[field_name]", you need to use c.update_attributes(params[:foo]))

Should I symbolize keys?

1) I am grabbing some records for the DB in HAML to display, and the attributes method on each row returns a hash. The hash's keys are strings. Should I turn those keys into symbols? I am not sure the call to symbolize_keys is worth it. I.e.,
%td #{app['comment']}
or
%td #{app[:comment]
2) I am trying to symbolize the array of hashes I return, but it is not working:
rows = Comment.all(:order => 'created DESC')
result = rows.each_with_object([]) do |row, comments|
comments << row.attributes.symbolize_keys
end
Is it not actually pushing the symbolized hash into the comments array? I also tried symbolize_keys!, and that did not help. What am I doing wrong?
Since you're using Rails, you have access to HashWithIndifferentAccess so you can bypass your "strings or symbols" issue quite easily by allow both:
h = HashWithIndifferentAccess.new(some_model.attributes)
puts h['id'] # Gives you some_model.id
puts h[:id] # Also gives you some_model.id
Your each_with_object approach:
result = rows.each_with_object([]) do |row, comments|
comments << row.attributes.symbolize_keys
end
should work fine so I think your problem with that lies elsewhere.
Do you have a reason for using ActiveRecord::Base#attributes[your_attribute] instead of ActiveRecord::Base#your_attribute directly? You didn't mention a reason.
ActiveRecord::Base automatically sets up accessors for your database fields:
object = Model.new
object.your_column = "foo" # Writer
object.your_column # Reader
You should be able to use the reader in your views instead of accessing the value through ActiveRecord::Base#attributes.
Update:
I'm not sure if this is what confuses you.
Comment.find(:all) already retrieves all columns values for those rows in your database and stores them in your Comment objects (which we assign to #comments below). The values are already stored in your Comment objects, so you may already use them in your views as you please.
In your controller, if you have:
def index
#comments = Commend.find(:all) # Fetch columns and rows.
end
you can do this in your HAML view:
- #comments.each do |comment| # Iterate through array of Comment objects
%tr
%td= comment.comment # Use value for "comment" column.
you can add hook, which symbolizes keys after model load:
class YourModel < ApplicationRecord
after_initialize do |rec|
attributes["some_json_field"].symbolize_keys! if attributes.key? "some_json_field"
end
end

deleting attributes from ActiveRecord model

I'm migrating data between two activerecord connections, I've got my models all setup correctly so I can read from say Legacy::Tablename and Tablename and insert it into the new table.
The problem I have is my new model doesn't have all of the attributes that are in the legacy model so I get an 'unknown attribute' when I try to create a record in the new model via;
legacy_users = Legacy::User.all
legacy_users.each do |legacy_user|
User.create legacy_user.attributes
end
however if I try to remove the offending attribute it still doesn't work eg.
legacy_user.attributes.delete 'some_attribute'
Can anyone offer any pointers?
How about attributes.except(:some_attribute)?
This should work in that case:
legacy_users = Legacy::User.all
legacy_users.each do |legacy_user|
u = User.new
u.attributes.each do |k, v|
old_val = legacy_user.send(k) # Get the attr from old user
u.send("#{k}=", old_val) # Set it to the new user
end
end
You won't need to go through the mess of removing each unused attribute too
I'm working on a migration as well, and in my case I was passing a block to first_or_create to clone objects. I couldn't get delete() or except() to work, but for some reason this works:
scrubbed_obj = my_obj.attributes.reject { |k,v| k == 'the_attribute_you_dont_want' }
new_object.attributes = scrubbed_obj
and then the block saves fine. Just throwing dropping this answer here in case anyone else is experiencing similar issues.

Doing something before saving a form used with HABTM

I currently have 3 tables.
snippet
tags
snippet_tags
I'm using HABTM.
So I did a form to save a snippet with tags. Keywords are in a text field, separated by commas.
What I need to do is to take the string from this text field, loop on the keywords, check if they exist, if not create them, and THEN save the snippet.
I tried with a before_save but it doesn't seem to go by that way..
So if you could help me, it'd great!
Thanks a lot!
I think JosephL's answer is pretty good. Although, I would do it all in the snippets_controller action:
def create
#snippet = Snippet.new(params[:snippet])
#snippet.tags = params[:tags].split(',').collect { |tag| Tag.find_or_create_by_name(tag) }
if #snippet.save
# do something when successful
else
# do something when saving failed
end
end
Sorry for that long, one-line statement. ;-)
I didn't test the code, but I hope it works.
Tag.find_or_create_by_name will do exactly that: when a tag with that name exists, it will return it, otherwise it will create the new tag on the fly and return that.
This way, the tags are already saved, before you call #snippet.save.
Please note, that I just assumed, how your variables and parameters are named.
Here is a version of your create method. The main change is not creating a Snippet_Tag. If your HABTM association is set up correctly then your snippet will have a tags collection which you can add your tags to. The collection will be persisted as Snippet_Tags by ActiveRecord. See the rails associations guide for more details on HABTM associations.
def create
# Creating the snippet
#snippet = Snippet.new
#snippet.title = params[:snippet][:title]
#snippet.content = params[:snippet][:content]
# loop through the tags
params[:snippet][:tags].split(',').collect do |tag_string|
tag_string.strip!
if tag_string.length > 0
# Find or create tag
tag = Tag.find_or_create_by_name(tag_string)
# Add tag to tags collection
#snippet.tags << tag
end
end
if #snippet.save
# do something when successful
else
# do something when saving failed
end
end
Use split to break your string into an array of the tags
Find each tag by name
If not found then create the tag
Add the tag to the snippet
Save the snippet (in your controller)
Example method to put in your snippet model
def add_tags(tag_list_string)
tag_array = tag_list_string.split ','
tag_array.each do |tag_name|
tag = (Tag.find_by_name(tag_name) || Tag.create(:name => tag_name))
self.tags << tag
end
end
Try before_update?

Resources