Rails - is this bad practice or can this be optimized? - ruby-on-rails

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

Related

Creating and populating Redmine custom fields by ruby code

We are developing a mogration from a small issue tracker software to Redmine. We use the Ruby classes directly to migrate the data. The class for an issue is defined like this:
class BuggyIssue < ActiveRecord::Base
self.table_name = :issues
belongs_to :last_issue_change, :class_name => 'BuggyIssueChange', :foreign_key => 'last_issue_change_id'
has_many :issue_changes, :class_name => 'BuggyIssueChange', :foreign_key => 'issue_id', :order => 'issue_changes.date DESC'
set_inheritance_column :none
# Issue changes: only migrate status changes and comments
has_many :issue_changes, :class_name => "BuggyIssueChange", :foreign_key => :issue_id
def attachments
#BuggyMigrate::BuggyAttachment.all(:conditions => ["type = 'issue' AND id = ?", self.id.to_s])
end
def issue_type
read_attribute(:type)
end
def summary
read_attribute(:summary).blank? ? "(no subject)" : read_attribute(:summary)
end
def description
read_attribute(:description).blank? ? summary : read_attribute(:description)
end
def time; Time.at(read_attribute(:time)) end
def changetime; Time.at(read_attribute(:changetime)) end
end
Creating an issue and defining custom fields for the issue works. However, populating the custom fields doesn't seem to work. There are 4 custom fields (Contact, Test status, Source and Resolution).
The custom fields are created like this:
repf = IssueCustomField.find_by_name("Contact")
repf ||= IssueCustomField.create(:name => "Contact", :field_format => 'string') if repf.nil?
repf.trackers = Tracker.find(:all)
repf.projects << product_map.values
repf.save!
The values for these fields are passed like this:
i = Issue.new :project => product_map[first_change.product_id],
...
:custom_field_values => {:Contact => issue.contact, 'Test status' => '', :Source => '', :Resolution => ''}
I've also tried a version with an index as hash key:
:custom_field_values => {'1' => issue.contact, 'Test status' => '', :Source => '', :Resolution => ''}
The issue can be saved without an issue, however, no value is ever passed over to Redmine. A
mysql> select count(*) from custom_values where value is not null;
+----------+
| count(*) |
+----------+
| 0 |
+----------+
1 row in set (0.01 sec)
shows that all values for the custom fields are NULL after the migration. I don't seem to be able to find how this is done correctly, the documentation for the Redmine classes is very sparse.
I spent much time to solve near same issue. Take a look on my code written to transfer data from old system to new via Redmine REST API. Cause I used ActiveResource code will be usable for you.
def update_custom_fields(issue, fields)
f_id = Hash.new { |hash, key| hash[key] = nil }
issue.available_custom_fields.each_with_index.map { |f,indx| f_id[f.name] = f.id }
field_list = []
fields.each do |name, value|
field_id = f_id[name].to_s
field_list << Hash[field_id, value]
end
issue.custom_field_values = field_list.reduce({},:merge)
raise issue.errors.full_messages.join(', ') unless issue.save
end
Now you can just call update_custom_fields(Issue.last, "MyField" => "MyValue" .. and so on)

Rails - Custom Validation For Having A Single Value Once

so I have these two models:
class Tag < ActiveRecord::Base
has_many :event_tags
attr_accessible :tag_id, :tag_type, :value
end
class EventTag < ActiveRecord::Base
belongs_to :tag
attr_accessible :tag_id, :event_id, :region
end
and this table for Tags:
**tag_id** **tag_type** **value**
1 "funLevel" "Boring..."
2 "funLevel" "A Little"
3 "funLevel" "Hellz ya"
4 "generic" "Needs less clowns"
5 "generic" "Lazer Tag"
...
What I would like to do is write a custom validation where it checks to see:
Each event_id has only one tag_type of "funLevel" attached to it, but can have more than one "generic" tags
For example:
t1 = EventTag.new(:tag_id => 1, :event_id =>777, :region => 'US')
t1.save # success
t2 = EventTag.new(:tag_id => 2, :event_id =>777, :region => 'US')
t2.save # failure
# because (event_id: 777) already has a tag_type of
# "funLevel" associated with it
t3 = EventTag.new(:tag_id => 4, :event_id =>777, :region => 'US')
t3.save # success, because as (tag_id:4) is not "funLevel" type
I have come up with one ugly solution:
def cannot_have_multiple_funLevel_tag
list_of_tag_ids = EventTag.where("event_id = ?", event_id).pluck(:tag_id)
if(Tag.where("tag_id in ?", list_of_tag_ids).pluck(:tag_type).include? "funLevel")
errors.add(:tag_id, "Already has a Fun Level Tag!")
end
Being new to rails, is there a more better/more elegant/more inexpensive way?
The way you have your data structured means that the inbuilt Rails validations are probably not going to be a heap of help to you. If the funLevel attribute was directly accessible by the EventTag class, you could just use something like:
# event_tag.rb
validate :tag_type, uniqueness: { scope: :event_id },
if: Proc.new { |tag| tag.tag_type == "funLevel" }
(unfortunately, from a quick test you don't seem to be able to validate the uniqueness of a virtual attribute.)
Without that, you're probably stuck using a custom validation. The obvious improvement to the custom validation you have (given it looks like you want to have the validation on EventTag) would be to not run the validation unless that EventTag is a funLevel tag:
def cannot_have_multiple_funLevel_tag
return unless self.tag.tag_type == "funLevel"
...
end

Difference of self, self[:item] in virtual attribute with a delegate

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

rendering specific fields with Rails

If I have an object say
#user
and I want to render only certain fields in it say first_name and last_name(I'm using AMF)
render :amf => #user
For instance I have a property for #user which is 'dob' (date of birth) I would like to use it inside the controller business logic but I don't want to send it to the client (in this case Flex) I can defenitaly do something like this before rendering:
#user.dob = nil
But I thought there must be a better way of doing this.
how do I do that?
I know I can use :select when doing the 'find' but I need to use the other field at the server side but don't want to send them with AMF to the client side and I don't want to do a second 'find'
Thanks,
Tam
This article gives the details for the approach.
You have configure the config/rubyamf_config.rb file as follows:
require 'app/configuration'
module RubyAMF
module Configuration
ClassMappings.ignore_fields = ['created_at','updated_at']
ClassMappings.translate_case = true
ClassMappings.assume_types = false
ParameterMappings.scaffolding = false
ClassMappings.register(
:actionscript => 'User',
:ruby => 'User',
:type => 'active_record',
:associations => ["employees"],
:ignore_fields => ["dob"]
:attributes => ["id", "name", "location", "created_at", "updated_at"]
)
ClassMappings.force_active_record_ids = true
ClassMappings.use_ruby_date_time = false
ClassMappings.use_array_collection = true
ClassMappings.check_for_associations = true
ParameterMappings.always_add_to_params = true
end
end

Ruby Style Question: storing hash constant with different possible values

This is more of a style question, I'm wondering what other people do.
Let's say I have a field in my database called "status" for a blog post. And I want it to have several possible values, like "draft", "awaiting review", and "posted", just as an example.
Obviously we don't want to "hard code" in these magic values each time, that wouldn't be DRY.
So what I sometimes do is something like this:
class Post
STATUS = {
:draft => "draft",
:awaiting_review => "awaiting review",
:posted => "posted"
}
...
end
Then I can write code referring to it later as STATUS[:draft] or Post::STATUS[:draft] etc.
This works ok, but there are a few things I don't like about it.
If you have a typo and call something like STATUS[:something_that_does_not_exist] it won't throw an error, it just returns nil, and may end up setting this in the database, etc before you ever notice a bug
It doesn't look clean or ruby-ish to write stuff like if some_var == Post::STATUS[:draft] ...
I dunno, something tells me there is a better way, but just wanted to see what other people do. Thanks!
You can use Hash.new and give it a block argument which is called if a key is unknown.
class Post
STATUS = Hash.new{ |hash, key| raise( "Key #{ key } is unknown" )}.update(
:draft => "draft",
:awaiting_review => "awaiting review",
:posted => "posted" )
end
It's a bit messy but it works.
irb(main):007:0> Post::STATUS[ :draft ]
=> "draft"
irb(main):008:0> Post::STATUS[ :bogus ]
RuntimeError: Key bogus is unknown
from (irb):2
from (irb):8:in `call'
from (irb):8:in `default'
from (irb):8:in `[]'
from (irb):8
This is a common problem. Consider something like this:
class Post < ActiveRecord::Base
validates_inclusion_of :status, :in => [:draft, :awaiting_review, :posted]
def status
read_attribute(:status).to_sym
end
def status= (value)
write_attribute(:status, value.to_s)
end
end
You can use a third-party ActiveRecord plugin called symbolize to make this even easier:
class Post < ActiveRecord::Base
symbolize :status
end
You could use a class method to raise an exception on a missing key:
class Post
def self.status(key)
statuses = {
:draft => "draft",
:awaiting_review => "awaiting review",
:posted => "posted"
}
raise StatusError unless statuses.has_key?(key)
statuses[key]
end
end
class StatusError < StandardError; end
Potentially, you could also use this method to store the statuses as integers in the database by changing your strings to integers (in the hash), converting your column types, and adding a getter and a setter.
I do it like this:
class Post
DRAFT = "draft"
AWAITING_REPLY = "awaiting reply"
POSTED = "posted"
STATUSES = [DRAFT, AWAITING_REPLY, POSTED]
validates_inclusion_of :status, :in => STATUSES
...
end
This way you get errors if you misspell one. If I have multiple sets of constants, I might do something like DRAFT_STATUS to distinguish.
Take a look at the attribute_mapper gem.
There's a related article that shows how you can handle the problem declaratively, like this (borrowed from the article):
class Post < ActiveRecord::Base
include AttributeMapper
map_attribute :status, :to => {
:draft => 1,
:reviewed => 2,
:published => 3
}
end
...which looks rather stylish.
Even though this is an old post, for somebody stumbling across this, you can use the fetch method on Hash, which raises an error (when no default is passed) if the given key is not found.
STATUS = {
:draft => "draft",
:awaiting_review => "awaiting review",
:posted => "posted"
}
STATUS.fetch(:draft) #=> "draft"
STATUS.fetch(:invalid_key) #=> KeyError: key not found: invalid_key

Resources