Metaprogramming in Ruby On Rails - ruby-on-rails

I have an array of strings: ["users", "torrents", "comments"]
these strings are the names of my bd tables.
how can I in .each loop connect to these tables and select|insert some data?

Avoid using eval
here is a simple solution using constantize
note: constantize will not allow arbitrary code to evaluated, it will just try to fetch a ruby constant, namely a Class
["users", "torrents", "comments"].each do |table_name|
# "users" => "User"
# or more complex
# "some_models" => "SomeModel"
#
class_name = table_name.singularize.camelize
# "User" => User
model_class = class_name.constantize
# do something with it
model_class.create!(:value => 12345)
end

Related

Extracting PDF rows to Rails db?

My source contains only 1,500 objects. At most it may grow to 2,000. I'm obtaining the objects via PDF and parsing with PDF Reader. They are parsed and returned by rows as String objects:
file = File.open("app/assets/images/file.pdf")
reader = PDF::Reader.new(file)
page = reader.pages[0]
rows = page.text.scan(/^.+/) #String Objects
rows.slice!(0..3) #Removes Header Info
Sample object :
=> ["1", "3", "215", "06/02/83", "Law,", "Steve"]
Then I remove the "," appended to last name and split the String object thus creating a Array Object:
row = #rows[0].tr(',', '').split #Array Objects
=> ["1", "3", "215", "06/02/83", "Law", "Steve"] #Array Object
I want to iterate through each row and insert into User table via console or form. What methods should I consider?
Thanks!
Lets assume you have mysql with users table in it and active_record gem installed.
If you writing just plain ruby script first you need to require active_record(commonly used ORM in rails), establish connection to db and create User model to get access to the users table via ActiveRecord:
require 'rubygems'
require 'active_record'
ActiveRecord::Base.establish_connection(
:adapter => 'mysql',
:host => 'localhost',
:database => 'your_db_name'
)
class User < ActiveRecord::Base
end
So now all you need to do is to iterate over the rows and call User.create method:
rows.slice!(0..3).each do |row|
row.reverse! # reverse array so we can easily access its elements with ruby Array methods
first_name = row.first
last_name = row.second.sub(/,/, '') # remove ',' symbol
birth_date = row.third
...
User.create(:first_name => first_name, :last_name => last_name, :birth_date => birth_date, ...) # I assumed you have first_name, last_name, ..., columns in your users table
end
Its all can be done in one file.
If you writing something like a rake task in your Rails environment you need to configure db connection in config/database.yml and create User model in app/models dir.

How do you call "contains?" on a MongoMapper Array in Rails 3?

I want to know how to check if an array element exists inside of a MongoMapper Array. This question is the closest I could find, but it addresses queries rather than simply using a document you already have.
My User model contains the line
key :roles, Array
The 'roles' array contains strings such as 'admin' or 'user.' For authorization, I need to call something like the following method on an instance of User:
if user.roles.contains?('admin')
# Do administrative stuff.
end
But when I try to call 'contains?' Ruby complains that there is no such method:
NoMethodError (undefined method `contains?' for #<Array:0x007fc845cd8948>):
app/models/ability.rb:11:in `initialize'
app/controllers/settings_controller.rb:5:in `index'
If there's no way to do this, then how do I convert the Array into a Ruby array to call 'contains?'? Calling to_a isn't doing it:
if user.roles.to_a.contains?('admin') # etc...
I'm using Rails 3.2.13, Ruby-1.9.3-p392, and MongoMapper 0.12.0 on Mountain Lion.
the function you are looking for is include?, so the expression would be: user.roles.include?('admin')
However since you mentioned mongomapper, if you were preforming a query on the roles array you would do the fallowing:
User.where( :roles => 'admin' )
You can also search an array with an array
User.where( :roles.in => ['admin'] )
for a query with admin or user you can do:
User.where( :$or => [{:roles => 'admin'},{:roles => 'user'}] )
and you can do and just the same:
User.where( :$and => [{:roles => 'admin'},{:roles => 'user'}] )

Is there find_or_create_by_ that takes a hash in Rails?

Here's some of my production code (I had to force line breaks):
task = Task.find_or_create_by_username_and_timestamp_and_des \
cription_and_driver_spec_and_driver_spec_origin(username,tim \
estamp,description,driver_spec,driver_spec_origin)
Yes, I'm trying to find or create a unique ActiveRecord::Base object. But in current form it's very ugly. Instead, I'd like to use something like this:
task = Task.SOME_METHOD :username => username, :timestamp => timestamp ...
I know about find_by_something key=>value, but it's not an option here. I need all values to be unique. Is there a method that'll do the same as find_or_create_by, but take a hash as an input? Or something else with similat semantics?
Rails 3.2 first introduced first_or_create to ActiveRecord. Not only does it have the requested functionality, but it also fits in the rest of the ActiveRecord relations:
Task.where(attributes).first_or_create
In Rails 3.0 and 3.1:
Task.where(attributes).first || Task.create(attributes)
In Rails 2.1 - 2.3:
Task.first(:conditions => attributes) || Task.create(attributes)
In the older versions, you could always write a method called find_or_create to encapsulate this if you'd like. Definitely done it myself in the past:
class Task
def self.find_or_create(attributes)
# add one of the implementations above
end
end
I also extend the #wuputah's method to take in an array of hashes, which is very useful when used inside db/seeds.rb
class ActiveRecord::Base
def self.find_or_create(attributes)
if attributes.is_a?(Array)
attributes.each do |attr|
self.find_or_create(attr)
end
else
self.first(:conditions => attributes) || self.create(attributes)
end
end
end
# Example
Country.find_or_create({:name => 'Aland Islands', :iso_code => 'AX'})
# take array of hashes
Country.find_or_create([
{:name => 'Aland Islands', :iso_code => 'AX'},
{:name => 'Albania', :iso_code => 'AL'},
{:name => 'Algeria', :iso_code => 'DZ'}
])

SearchLogic problems due to caching of method calls

I am trying to use the searchlogic gem to perform searches over a couple tables Post has_many assets. I need it to perform left outer joins rather than inner joins in the event of a non-existant asset.
From what I have below the query is generated with the required outer joins, and passes the first three tests, but fails on the last. However, if I only run the last test it then passes.
The reason for the failing is that the #search_logic_filter var is only being set on the first test and is used for all of the remaining tests.
The reason for the setting of the #search_logic_filter in this way is that it is the only call to method_missing that carries the param passed to the dynamic searchlogic method call of Post.title_or_body_or...like("fun")
Is there a better way to set the filter param?
test "find posts and assets by filter for user" do
customer = users(:customer)
create_post_for_user(customer, {:body => "Rails is fun", :tags => "rails ruby"})
create_post_for_user(customer, {:body => "Fun is what Emacs is all about", :title => "emacs"})
# File with post
asset_post = create_post_for_user(customer, {:body => "Ruby is pretty fun too",
:tags => "ruby"})
asset_post.assets << Asset.new(:upload_file_name => "ruby_tips",
:upload_file_size => 100,
:upload_content_type => "text")
asset_post.save
# search post
assert_equal 3, Post.find_for_user(customer.id, "fun").size
assert_equal 2, Post.find_for_user(customer.id, "ruby").size
assert_equal 1, Post.find_for_user(customer.id, "emacs").size
# search asset
puts "about to run last test"
assert_equal 1, Post.find_for_user(customer.id, "ruby_tips").size
end
class Post < ActiveRecord::Base
def self.find_for_user(user_id, filter, page=1)
Post.
user_id_equals(user_id).
title_or_body_or_tags_or_assets_upload_file_name_like(filter).all
end
class << self
def method_missing(name, *args, &block)
if name.to_s =~ /\w+_or_\w+_like$/
# ** only gets here once **
#search_logic_filter = args.first
super
elsif name == :assets_upload_file_name_like
# args is [] here which is the reason for the above setting of #search_logic_filter
named_scope :assets_upload_file_name_like, lambda {
{:joins => "left outer join assets on posts.id = assets.post_id",
:conditions => "assets.upload_file_name like '%#{#search_logic_filter}%'"}
}
assets_upload_file_name_like
else
super
end
end
end
end
** update
This is the query that is run for the final test. Notice that the upload_file_name param is 'fun', not 'ruby_tips'. The 'fun' param exists for all the tests for the upload_file_name col, but it only matters for the last test.
SELECT `posts`.*
FROM `posts`
left outer join assets
on posts.id = assets.post_id
WHERE (
((posts.title LIKE '%ruby_tips%') OR (posts.body LIKE '%ruby_tips%') OR (posts.tags LIKE '%ruby_tips%') OR (assets.upload_file_name like '%fun%'))
AND (posts.user_id = 20549131)
)
You should not declare the named_scope assets_upload_file_name_like that way. When it's called the first time, the assets_upload_file_name_like named scope is defined with the the value for :conditions generated according the value of #search_logic_filter at that time. You should set the parameter on the lambda instead.
There's also no need to use method_missing. Just declare the named_scope within the Post class. As a bonus, the query should be filtered to guard against SQL injection attacks.
class Post < ActiveRecord::Base
named_scope :assets_upload_file_name_like, lambda { |file_name| {
:joins => "left outer join assets on posts.id = assets.post_id",
# Prevent SQL injection.
:conditions => ["assets.upload_file_name like ?", "%#{file_name}%"]
}}
end

How to convert a Ruby object to JSON

I would like to do something like this:
require 'json'
class Person
attr_accessor :fname, :lname
end
p = Person.new
p.fname = "Mike"
p.lname = "Smith"
p.to_json
Is it possible?
Yes, you can do it with to_json.
You may need to require 'json' if you're not running Rails.
To make your Ruby class JSON-friendly without touching Rails, you'd define two methods:
to_json, which returns a JSON object
as_json, which returns a hash representation of the object
When your object responds properly to both to_json and as_json, it can behave properly even when it is nested deep inside other standard classes like Array and/or Hash:
#!/usr/bin/env ruby
require 'json'
class Person
attr_accessor :fname, :lname
def as_json(options={})
{
fname: #fname,
lname: #lname
}
end
def to_json(*options)
as_json(*options).to_json(*options)
end
end
p = Person.new
p.fname = "Mike"
p.lname = "Smith"
# case 1
puts p.to_json # output: {"fname":"Mike","lname":"Smith"}
# case 2
puts [p].to_json # output: [{"fname":"Mike","lname":"Smith"}]
# case 3
h = {:some_key => p}
puts h.to_json # output: {"some_key":{"fname":"Mike","lname":"Smith"}}
puts JSON.pretty_generate(h) # output
# {
# "some_key": {
# "fname": "Mike",
# "lname": "Smith"
# }
# }
Also see "Using custom to_json method in nested objects".
Try it. If you're using Ruby on Rails (and the tags say you are), I think this exact code should work already, without requiring anything.
Rails supports JSON output from controllers, so it already pulls in all of the JSON serialization code that you will ever need. If you're planning to output this data through a controller, you might be able to save time by just writing
render :json => #person

Resources