Difference of self, self[:item] in virtual attribute with a delegate - ruby-on-rails

there is something that confuses me a little bit and i would like some clarification please, since it causes me some trouble.
I have a city model with a delegate to a wood_production attribute that specifies the amount of wood of that city. It's like:
has_one :wood_production, :autosave => true
delegate :amount, :to => :wood_production, :prefix => true, :allow_nil => true
def wood
wood_production_amount
end
def wood= amt
self[:wood_production_amount] = amt
end
I normally wanted to be able to do a city.wood -= 1000 and save that value through the city, but i've come into all sorts of problems doings this. It seems that i am not setting my virtual attributes correctly maybe.
So i would actually like to ask, what is the difference between these :
def wood
self.wood_production_amount
end
def wood
wood_production_amount
end
def wood
self[:wood_production_amount]
end
and what should really be used to correctly handle the situation ?
EDIT :
If i create the setter like :
def wood= amt
self.wood_production_amount = amt
end
I get :
1.9.2p290 :003 > c.wood -= 1000
=> 58195.895014789254
1.9.2p290 :004 > c.save
(0.1ms) BEGIN
(0.3ms) UPDATE `wood_productions` SET `amount` = 58195.895014789254, `updated_at` = '2012-01-24 02:13:00' WHERE `wood_productions`.`id` = 1
(2.0ms) COMMIT
=> true
1.9.2p290 :005 > c.wood
=> 66522.63434300483 ???????
Buf if the setter is :
def wood= amt
wood_production_amount = amt
end
1.9.2p290 :004 > c.wood -= 1000
=> 58194.823000923556
1.9.2p290 :005 > c.save
(0.1ms) BEGIN
(0.2ms) COMMIT
=> true

Answering the first part, self.wood_production_amount and wood_production_amount are functionally identical. The only difference is that in the latter, self is implied, being the current instance of the City model. I rarely use self.anything unless it's required.
self[:wood_production_amount] is functionally similar to the first two in most cases. The difference is that it allows you to easily overwrite default accessor methods. read_attribute(:attribute) is functionally identical to self[:attribute]. For example, say your City model has a state attribute, but you want to always return the state in uppercase when it is requested. You could do something like this:
class City < ActiveRecord::Base
def state
self[:state].try(:upcase)
# or
read_attribute(:state).try(:upcase)
end
end
city = City.new(:state => 'vermont')
city.state # => VERMONT
So to answer your second question, it really depends on how you want to use it. Personally, I would go with the delegate method unless you need to overwrite some behavior. The reason it wasn't working for you might be that you aren't delegating the setter method :amount= as well:
delegate :amount, :amount= :to => :wood_production,
:prefix => true, :allow_nil => true

Related

In Rails, how do I limit which attributes can be updated, without preventing them from being created?

I have a situation where an attribute can be created through a JSON API. But once it is created, I want to prevent it from ever being updated.
This constraint causes my first solution, which is using attr_accessible, to be insufficient. Is there a nice way to handle this type of situation in rails, or do I have to perform a manual check in the update method?
You can use attr_readonly, this will allow the value to be set on creation, but ignored on update.
Example:
class User < ActiveRecord::Base
attr_accessible :name
attr_readonly :name
end
> User.create(name: "lorem")
> u = User.first
=> #<User id: 1, name: "lorem">
> u.name = "ipsum"
=> "ipsum"
> u.save
=> true
> User.first.name
=> "lorem"
There is not a nice way to do that as far as I know, you have to write a custom filter
before_update :prevent_attributes_update
def prevent_attribute_updates
%w(attr1, attr2).each do |a|
send("#{attr1}=", send("#{attr1}_was")) unless self.send("#{attr1}_was").blank?
end
end

Rails has_many relation: a look at object_ids

Something strange is happening in Rails 2.3.14. Any ideas why the following happens?
Example
Suppose we have classes like
class Article < ActiveRecord::Base
has_many :prices
end
class Price < ActiveRecord::Base
end
Then in a irb session the following happens.
>> a = Article.first
=> #<Article id: 980190962>
>> a.prices.first.object_id
=> 97498070
>> a.prices.first.object_id
=> 97470500
>> a.prices.first.object_id
=> 97451010
>> a.valid?
=> true
>> a.prices.first.object_id
=> 97374790
>> a.prices.first.object_id
=> 97374790
So at first the object_id changes each time the record is accessed (yes, it's always the same one). Later after a call to #valid? this behavior stops. Instead everything is fine. You get the same object on each call.
Why is this important?
Let's assume you add a validation to Price
class Price < ActiveRecord::Base
validates_presence_of :amount
end
Then you want to change a price of an article.
>> a = Article.first
=> #<Article id: 980190962>
>> p = a.prices.first
=> #<Price id: 280438907, amount: 1.0, article_id: 980190962>
>> p.amount = nil # oops, accidentally we assigned nil
=> nil
>> p.valid?
=> false
>> a.valid?
=> true
What's that? The price is invalid, but the article is not? This shouldn't have happend, because by default the :validate-option of has_many is set to true. This happens because of the changing object_ids
Every new context(request/response) will regenerate the object IDs. As you are running the query a.prices.first.object_id it will hit the query to get the first price every time and as each hit will generate new object IDs.
But in second case you have extracted first price into a instance variable and working on that so no new request/response is going to database hence it is giving same object IDS.

Why won't this datamapper object save?

Here's the model:
class Target
include DataMapper::Resource
property :id, Serial
property :owed, Integer, :default => 0, :required => true
property :served, Integer, :default => 0, :required => true
def go
#owed -= 1
#served += 1
save
end
end
When I do this:
t = Target.first
t.go
It doesn't seem to update the database. Any ideas?
The resource isn't saving because DataMapper doesn't know that the owed and served properties have been changed. #save will only save the resource, if the resource is considered dirty by DataMapper.
Instead, change the values of self.owed and self.served, which will cause a state change within the resource, which will mark the resource as dirty and allow #save to trigger. Additionally, you can simplify the go method using #update:
def go
update(:owed => self.owed - 1, :served => self.served + 1)
end
You need to use self.owed and self.served instead:
def go
self.owed -= 1
self.served += 1
save
end

Rails - is this bad practice or can this be optimized?

Would this be considered bad practice?
unless Link.exists?(:href => 'example.com/somepage')
Domain.where(:domain => 'example.com').first.links.create(:href => 'example.com/somepage', :text => 'Some Page')
end
I realize I might be requesting more data then I actually need, can I optimize this somehow?
Domain is a unique index so the lookup should be fairly quick.
Running Rails 3.0.7
You can refactor your code in this manner:
Domain class
class Domain < ActiveRecord::Base
has_many :links
end
Link class
class Link < ActiveRecord::Base
belongs_to :domain
validates :href,
:uniqueness => true
attr :domain_url
def domain_url=(main_domain_url)
self.domain = Domain.where(domain: main_domain_url).first ||
Domain.new(domain: main_domain_url)
end
def domain_url
self.domain.nil? ? '' : self.domain.domain_url
end
end
Usage
Link.create(href: 'example.com/somepage',
text: 'Some Page',
domain_url: 'example.com')
Conclusion
In both cases (your and mine) you get two request (like so):
Domain Load (1.0ms) SELECT "domains".* FROM "domains" WHERE "domains"."domain" = 'example.com' LIMIT 1
AREL (0.1ms) INSERT INTO "links" ("href", "text", "domain_id", "created_at", "updated_at") VALUES ('example.com/somepage', 'Some Page', 5, '2011-04-26 08:51:20.373523', '2011-04-26 08:51:20.373523')
But with this code you're also protected from unknown domains, so Link'd create one automatically.
Also you can use validates uniqueness so you can remove all unless Link.exists?(:href => '...').
Domain.where(:domain => 'example.com').
first.links.
find_or_create_by_href_and_text(:href => 'example.com/somepage', :text => "Some Page")
UPD
#domain = Domain.where(:domain => 'example.com').
first.links.
find_or_create_by_href('example.com/somepage')
#domain.text = "My Text"
#domain.save
Or you can use extended update_or_create_by_* method:
Domain.update_or_create_by_href('example.com/somepage') do |domain|
domain.text = "My Text"
end
More info here:
find_or_create_by in Rails 3 and updating for creating records

Trying to master Ruby. How can I optimize this method?

I'm learning new tricks all the time and I'm always on the lookout for better ideas.
I have this rather ugly method. How would you clean it up?
def self.likesit(user_id, params)
game_id = params[:game_id]
videolink_id = params[:videolink_id]
like_type = params[:like_type]
return false if like_type.nil?
if like_type == "videolink"
liked = Like.where(:user_id => user_id, :likeable_id => videolink_id, :likeable_type => "Videolink").first unless videolink_id.nil?
elsif like_type == "game"
liked = Like.where(:user_id => user_id, :likeable_id => game_id, :likeable_type => "Game").first unless game_id.nil?
end
if liked.present?
liked.amount = 1
liked.save
return true
else # not voted on before...create Like record
if like_type == "videolink"
Like.create(:user_id => user_id, :likeable_id => videolink_id, :likeable_type => "Videolink", :amount => 1)
elsif like_type == "game"
Like.create(:user_id => user_id, :likeable_id => game_id, :likeable_type => "Game", :amount => 1)
end
return true
end
return false
end
I would do something like:
class User < ActiveRecord::Base
has_many :likes, :dependent => :destroy
def likes_the(obj)
like = likes.find_or_initialize_by_likeable_type_and_likeable_id(obj.class.name, obj.id)
like.amount += 1
like.save
end
end
User.first.likes_the(VideoLink.first)
First, I think its wrong to deal with the "params" hash on the model level. To me its a red flag when you pass the entire params hash to a model. Thats in the scope of your controllers, your models should have no knowledge of the structure of your params hash, imo.
Second, I think its always cleaner to use objects when possible instead of class methods. What you are doing deals with an object, no reason to perform this on the class level. And finding the objects should be trivial in your controllers. After all this is the purpose of the controllers. To glue everything together.
Finally, eliminate all of the "return false" and "return true" madness. The save method takes care of that. The last "return false" in your method will never be called, because the if else clause above prevents it. In my opinion you should rarely be calling "return" in ruby, since ruby always returns the last evaluated line. In only use return if its at the very top of the method to handle an exception.
Hope this helps.
I'm not sure what the rest of your code looks like but you might consider this as a replacement:
def self.likesit(user_id, params)
return false unless params[:like_type]
query = {:user_id => user_id,
:likeable_id => eval("params[:#{params[:like_type]}_id]"),
:likeable_type => params[:like_type].capitalize}
if (liked = Like.where(query).first).present?
liked.amount = 1
liked.save
else # not voted on before...create Like record
Like.create(query.merge({:amount => 1}))
end
end
I assume liked.save and Like.create return true if they are succesful, otherwise nil is returned. And what about the unless game_id.nil? ? Do you really need that? If it's nil, it's nil and saved as nil. But you might as well check in your data model for nil's. (validations or something)

Resources