So I've implemented a hack and I want to know what the "proper" way is to do it.
The issue is that I have an *_attributes=() method that uses an instance variable. The reason this is a problem is that at the time the method is called, that instance variable hasn't been set. Here is the method in question:
def proposed_times_attributes=(attributes)
attributes.each do |key,value|
value[:timezone] = timezone
end
assign_nested_attributes_for_collection_association(:proposed_times, attributes)
end
The timezone is in the params hash after proposed_times_attributes. Therefore my hack is to delete it from params, then add it back, thus moving it to the end of the line.
def create
p = params[:consultation]
a = p.delete(:proposed_times_attributes)
p[:proposed_times_attributes] = a
#consultation = current_user.advised_consultations.new(p)
...
end
What is the proper way that I should be doing this?
new() calls load() where the loop is that goes through each key/value pair.
Thankfully I'm using Ruby 1.9.2 which keeps the order, but it would be nice to know how to do this so that it wouldn't depend on this fact.
If the next operation after new will always be a save operation, you can store the attributes in an accessor, and use a before_validation callback to operate on them as you wish.
class Consultations < ActiveRecord::Base
attr_accessor :proposed_times_attributes
before_validation :assign_proposed_times
def assign_proposed_times
proposed_times_attributes.each do |key,value|
value[:timezone] = timezone
end
assign_nested_attributes_for_collection_association(:proposed_times, attributes)
end
end
Now in your controller you simply have:
def create
#consultation = current_user.advised_consultations.new(params[:consultation])
...
end
If you wish to do other operations before calling save, then pulling out the param as you did in your example, then passing it to an appropriate method after calling new would be the way to go.
Related
Is there a better way to set values to setter methods when they are made dynamically using attr_accessor method? I need this for setting values for them from another model in rails. I'm trying to do something like below.
Model_class.all.each do |mdl|
attr_accessor(mdl.some_field)
end
Then I know that it creates a set of get and setter methods. What I want to do is, when these methods are get created, i want some value to be specified for setter method.Thanks in advance.
attr_accessor has no magic embedded. For each of params passed to it, it basically executes something like (the code is simplified and lacks necessary checks etc):
def attr_accessor(*vars)
vars.each do |var|
define_method var { instance_variable_get("##{var}") }
define_method "#{var}=" { |val| instance_variable_set("##{var}", val) }
end
end
That said, the attr_accessor :var1, :var2 DSL simply brings new 4 plain old good ruby methods. For what you are asking, one might take care about defining these methods (or some of them, or none,) themselves. For instance, for cumbersome setting with checks one might do:
attr_reader :variable # reader is reader, no magic
def variable=(val) do
raise ArgumentError, "You must be kidding" if val.nil?
#variable = val
end
The above is called as usual:
instance.variable = 42
#⇒ 42
instance.variable = nil
#⇒ ArgumentError: You must be kidding
Here is another possible implementation for this:
def attr_accessor(*args)
args.each do |attribute|
define_method(attribute.to_sym) { eval("##{attribute}") }
define_method((attribute.to_s + '=').to_sym) {|value| eval("##{attribute} = value") }
end
end
I would like to do something like:
class TestController < InheritedResources::Base
def test_method
self.var1 + self.var2
end
private
def test_params
params.require(:test).permit(:var1, :var2)
end
end
Where in the view I could call from the built in controller index:
test.test_method
I've tried adding a create method to the controller as follows:
def create
Test.create!(require(:test).permit(:var1, :var2, :test_method))
end
I've also tried updating the params directly:
private
def test_params
params.require(:test).permit(:var1, :var2, :test_method)
end
I've also tried making a helper method, but I knew that was doomed to fail because it wouldn't have access to var1 and var2.
I guess I just don't understand two things: one how to make my var1 and var2 white-listed so I can use them, and more importantly how to add a method to my model using strong parameters, because attr_accessible doesn't work in my models anymore.
EDIT:
Let me rephrase a little, maybe it will help. I can get access to individual Test objects in my view with a simple call to tests.each |test| in the view. I just want to make methods that act on my already defined active record variables for that object, hence var1 and var2. The problem is when I define a new method in my controller it is private to the object and I won't have access to it with a call from an instance of the object. Better yet, I would like to just be able to define a new variable, local to the object, that is created after it has propagated its other fields from the db.
EDIT2: I'm aware I'm probably missing the design pattern here. It can be hard to describe that I want X, when really I need Z. Thanks for the patience.
Thanks for the help.
There's no reason for white-listing parameters that you'll directly use.
White-listing with strong parameters is useful only when you call function like ActiveRecord#update that simply take every key from the dictionary, so you can control with key you want to allow and which not.
In this case, just do:
class TestController < InheritedResources::Base
def test_method
#result = params[:var1] + params[:var2]
end
end
And in your view, just print the #result variable wherever you want
<%= #result %>
This is the Rails way. You can of course call the variable as you want.
Helper methods are useful only for more complex cases.
I have a method like this:
class Foo < ActiveRecord::Base
def load_data(data)
self.foo = data[:foo] if data.has_key?(:foo)
self.bar = data[:bar] if data.has_key?(:bar)
self.moo = data[:moo] if data.has_key?(:moo)
self.save
end
end
I want to write the method like this:
[:foo, :bar, :moo].each do |sym|
# need some trick here
self.sym = data[sym] if data.has_key?(sym)
end
Of course this method doesn't work, how can I assign a value to a Model column by using a symbol?
vee's answer is correct for the general case, but since this is Rails and ActiveRecord, you can take some nice shortcuts:
def load_data(data)
update_attributes data.slice(:foo, :bar:, :moo)
end
This works because data.slice filters your data hash to just the given keys, and then update_attributes will set those values in your model and invoke #save. When the keys aren't present, they aren't written, so you don't need to check and assign each key separately.
If you don't care about filtering the inbound data and simply assigning the keys given to the model, then just update_attributes data and you're done.
You can use send:
[:foo, :bar, :moo].each do |sym|
# need some trick here
send "#{sym}=", data[sym] if data.has_key?(sym)
end
I'd appreciate any help I can get with a somewhat strange phenonemon going on in my code. The controller's create method is (roughly) as follows:
def create
#session ||= Session.new
#session.date = params[:date]
#session.generate_string
#session.save
# etc
end
And the model:
class Session < ActiveRecord::Base # table is 'sessions' with 3 columns :id, :str, :date
include MyHelper
def generate_string(num_chars)
#str ||= ""
num_chars.to_i.times do
#str += some_method_in_MyHelper() # method returns a string
end
end
end
With some logging I found out that although the generate_string is working correctly, the resulting #session (in the controller) has the date set as expected but the value of str is a blank string. Sure enough, when the .save is hit, the database is told to insert a record consisting of a blank string and the correct date.
I found this Why do my changes to model instances not get saved sometimes in Rails 3? that suggests I should be using the "self" prefix instead of #. This seems to make the code work as expected, but seems strange because I thought self.xxx referred to the class, not the class instance. I'd be grateful if anyone could clarify what's going on - thanks!
self refers to the instance when used inside an instance method. It refers to the class outside an instance method, when it (self) is the class that's being defined.
# is an instance variable, which is different than an ActiveRecord column.
In order to store it in the str field to be saved to the database, you need to use self.str method. I think this is what you are looking for
class Session < ActiveRecord::Base # table is 'sessions' with 3 columns :id, :str, :date
include MyHelper
def generate_string(num_chars)
str = ""
num_chars.to_i.times do
str += some_method_in_MyHelper() # method returns a string
end
self.str = str # store it in attribute to be saved into db
end
end
Notice I removed the instance variable #str and changed it to local variable str instead because it seems like you want to generate a new string everytime this method is called? Also, this variable caching is useless
#session ||= Session.new
because instance variables only stick around for a single request. It should be
#session = Session.new
I have an ActiveRecord model with several virtual attribute setters. I want to build an object but not save it to the database. One setter must execute before the others. How to do?
As a workaround, I build the object in two steps
#other_model = #some_model.build_other_model
#other_model.setup(params[:other_model)
Where setup is:
class OtherModel < ActiveRecord::Base
def setup(other_params)
# execute the important_attribute= setter first
important_attribute = other_params.delete(:important_attribute)
# set the other attributes in whatever order they occur in the params hash
other_params.each { |k,v| self.send("#{k}=",v) }
end
end
This seems to work, but looks kludgy. Is there a better way?
EDIT
per neutrino's suggestion, I added a method to SomeModel:
class SomeModel < ActiveRecord::Base
def build_other_model(other_params)
other_model = OtherModel.new(:some_model=>self)
other_model.setup(other_params)
other_model
end
end
It's a good thing that you have this manipulations done in an OtherModel's method, because you can just call this method and not worry about the order of assignments. So I would leave this part but just call it from a SomeModel's method:
class SomeModel < ActiveRecord::Base
def build_other_model(other_params)
other_model = build_other_model
other_model.setup(other_params)
other_model
end
end
So then you would have
#other_model = #some_model.build_other_model(params[:other_model])
I took your idea of deleting the important attribute first in your setup method, but used alias_chain_method instead to make it more of a transparent process:
def attributes_with_set_important_attribute_first=(attributes = {})
# Make sure not to accidentally blank out the important_attribute when none is passed in
if attributes.symbolize_keys!.include?(:important_attribute)
self.important_attribute = attributes.delete(:important_attribute)
end
self.attributes_without_set_important_attribute_first = attributes
end
alias_method_chain :attributes=, :set_important_attribute_first
This way none of your code should change from the normal Rails style
#other_model = #some_model.other_models.build(params[:other_model])