How to join values from hash-maps? - sql

i fetched some SQL tables via JDBC and I need to do another transformation before i create a new table and insert values.
I got these two hash-maps:
Chapters:
(
{:chapter_uuid "b7984dde-50a1-4147-bfee-95bbd068e031", :l1_chapter_text
120876M, :l2_chapter_text nil, :l3_chapter_text nil, :l4_chapter_text nil}
{:chapter_uuid "23df4f27-534b-4cdb-81ed-dbdc8b9b140c", :l1_chapter_text
120880M, :l2_chapter_text 120876M, :l3_chapter_text nil, :l4_chapter_text
121621M}
)
Translation:
(
{:translation_id 3258779M, :translation_text 120876M, :translation_language "cs",
:translation_name "Vnější fasáda"}
{:translation_id 3258780M, :translation_text 120876M, :translation_language "en",
:translation_name "Exterior Signage"}
{:translation_id 3258782M, :translation_text 120880M, :translation_language "cs",
:translation_name "Čistá výloha"}
{:translation_id 3258783M, :translation_text 121621M, :translation_language "cs",
:translation_name "Vnější signalizace"}
{:translation_id 3258784M, :translation_text 121621M, :translation_language "en",
:translation_name "Pre-signalization"}
)
This is what i need to get:
Where Chapters.lx_chapter_text = Translation.translation_text -> swap Chapters.lx_chapter_text with translation_name and insert :language value -> Make sure that every language has its own hash-map!
(
{:chapter_uuid "b7984dde-50a1-4147-bfee-95bbd068e031", :l1_chapter_text
"Vnější fasáda", :l2_chapter_text nil, :l3_chapter_text nil, :l4_chapter_text nil
:language "cs"}
{:chapter_uuid "b7984dde-50a1-4147-bfee-95bbd068e031", :l1_chapter_text
"Exterior Signage", :l2_chapter_text nil, :l3_chapter_text nil, :l4_chapter_text nil
:language "en"}
{:chapter_uuid "23df4f27-534b-4cdb-81ed-dbdc8b9b140c", :l1_chapter_text
"Čistá výloha", :l2_chapter_text "Vnější fasáda", :l3_chapter_text nil, :l4_chapter_text
"Vnější signalizace" :language "cs"}
{:chapter_uuid "23df4f27-534b-4cdb-81ed-dbdc8b9b140c", :l1_chapter_text
120880M, :l2_chapter_text "Exterior Signage", :l3_chapter_text nil, :l4_chapter_text
"Pre-signalization" :language "en"}
)
This is how far i´ve got:
but as you can see this is not right (bad nested)
Can you tell me how to do it right? Thank you!
(defn test_table []
(for [language (distinct(map #(:translation_language %) translation))]
(for [chapter chapters]
(for [text translation]
(cond
(and (= (:l1_chapter_text chapter) (:translation_text text)) (= (:translation_language text) language))
(assoc chapter :l1_chapter_text (:translation_name text) :language (:translation_language text))
(and (= (:l2_chapter_text chapter) (:translation_text text)) (= (:translation_language text) language))
(assoc chapter :l2_chapter_text (:translation_name text) :language (:translation_language text))
(and (= (:l3_chapter_text chapter) (:translation_text text)) (= (:translation_language text) language))
(assoc chapter :l3_chapter_text (:translation_name text) :language (:translation_language text))
(and (= (:l4_chapter_text chapter) (:translation_text text)) (= (:translation_language text) language))
(assoc chapter :l4_chapter_text (:translation_name text) :language (:translation_language text))))))
(test-table)
->
((({:chapter_uuid "b7984dde-50a1-4147-bfee-95bbd068e031", :l1_chapter_text "Vnější fasáda",
:l2_chapter_text nil, :l3_chapter_text nil, :l4_chapter_text nil, :language "cs"}
nil nil nil nil)
({:chapter_uuid "23df4f27-534b-4cdb-81ed-dbdc8b9b140c", :l1_chapter_text 120880M,
:l2_chapter_text "Vnější fasáda", :l3_chapter_text nil, :l4_chapter_text 121621M, :language "cs"}
nil
{:chapter_uuid "23df4f27-534b-4cdb-81ed-dbdc8b9b140c", :l1_chapter_text "Čistá výloha",
:l2_chapter_text 120876M, :l3_chapter_text nil, :l4_chapter_text 121621M, :language "cs"}
{:chapter_uuid "23df4f27-534b-4cdb-81ed-dbdc8b9b140c", :l1_chapter_text 120880M,
:l2_chapter_text 120876M, :l3_chapter_text nil, :l4_chapter_text "Vnější signalizace", :language "cs"}
nil))
((nil
{:chapter_uuid "b7984dde-50a1-4147-bfee-95bbd068e031", :l1_chapter_text "Exterior Signage",
:l2_chapter_text nil, :l3_chapter_text nil, :l4_chapter_text nil, :language "en"}
nil nil nil)
(nil
{:chapter_uuid "23df4f27-534b-4cdb-81ed-dbdc8b9b140c", :l1_chapter_text 120880M,
:l2_chapter_text "Exterior Signage", :l3_chapter_text nil, :l4_chapter_text 121621M, :language "en"}
nil nil
{:chapter_uuid "23df4f27-534b-4cdb-81ed-dbdc8b9b140c", :l1_chapter_text 120880M,
:l2_chapter_text 120876M, :l3_chapter_text nil, :l4_chapter_text "Pre-signalization", :language "en"})))

First, do you have a list of all languages that are used in translations? If not, let's derive it from the translations collection:
(def langs (distinct (map :translation_language translations))) ;; => ("cs" "en")
Second, as you are going to use the translations collection as a lookup table, it makes sense to make a map of it. The easiest (though maybe not the most efficient) way to do it is to use the group-by function:
(def translations-map
(group-by (juxt :translation_text :translation_language) translations))
Looks like what you need to do is to convert each :l<#>_chapter_text to the actual text, or keep the id if the translation is not available in the given language. Using the map we have created, it is almost trivial:
(defn translate [id lang]
(or (:translation_name (first (get translations-map [id lang])))
id))
Now, let's put it all together, taking each chapter and, for each language, trying to replace all ids with translations:
(for [chapter chapters
lang langs]
(-> chapter
(assoc :language lang)
(update :l1_chapter_text translate lang)
(update :l2_chapter_text translate lang)
(update :l3_chapter_text translate lang)
(update :l4_chapter_text translate lang)))
This should return the desired result.

(let [a ...
b ...]
(as-> [:l1_chapter_text :l2_chapter_text :l3_chapter_text :l4_chapter_text]
relations
(interleave relations (repeat :translation_text))
(apply hash-map relations)
(mapcat #(clojure.set/join a b (into {} [%])) relations)
(group-by (fn [a] (apply str ((juxt :chapter_uuid :l1_chapter_text :l2_chapter_text :l3_chapter_text :l4_chapter_text :translation_language) a))) relations)
(vals relations)))

Related

How to let the SQL results display in Rails's log?

I don't know whether this is a reasonable request. I hope to see the result in the log file such as log/development.log, Now the ActiveRecord::Base.logger.level = 0 and there is only the SQL statements:
User Load (0.6ms) SELECT "users".* FROM "users" WHERE "users"."id" = 3 LIMIT 1
Excursion Load (0.3ms) SELECT "excursions".* FROM "excursions" WHERE "excursions"."id" = $1 LIMIT 1 [["id", "5"]]
Actor Load (0.5ms) SELECT "actors".* FROM "actors" WHERE "actors"."id" = 4 LIMIT 1
There is no results. I know in the 'rails console', when using ActiveRecord::Base.logger = Logger.new(STDOUT), I see the statements and corresponding results:
1.9.3-p551 :001 > ActiveRecord::Base.logger = Logger.new(STDOUT)
=> #<Logger:0x007f80441364d0 #progname=nil, #level=0, #default_formatter=#<Logger::Formatter:0x007f8044135c60 #datetime_format=nil>, #formatter=# <Logger::SimpleFormatter:0x007f8044134bd0 #datetime_format=nil>, #logdev=#<Logger::LogDevice:0x007f8044135440 #shift_size=nil, #shift_age=nil, #filename=nil, #dev=#<IO:<STDOUT>>, #mutex=# <Logger::LogDevice::LogDeviceMutex:0x007f8044135378 #mon_owner=nil, #mon_count=0, #mon_mutex=#<Mutex:0x007f8044134c98>>>>
1.9.3-p551 :002 > User.first
User Load (1.0ms) SELECT "users".* FROM "users" LIMIT 1
Actor Load (0.8ms) SELECT "actors".* FROM "actors" WHERE "actors"."id" = 2 LIMIT 1
ActivityObject Load (0.9ms) SELECT "activity_objects".* FROM "activity_objects" WHERE "activity_objects"."id" = 2 LIMIT 1
=> #<User id: 1, encrypted_password: "$2a$10$wFj.Q9XX0ua16BF0.SIps.VjnfVi8/6egicirc/SFxw3...", password_salt: nil, reset_password_token: nil, reset_password_sent_at: nil, remember_created_at: nil, sign_in_count: 0, current_sign_in_at: nil, last_sign_in_at: nil, current_sign_in_ip: nil, last_sign_in_ip: nil, authentication_token: nil, created_at: "2015-03-25 22:14:32", updated_at: "2015-03-25 22:14:32", actor_id: 2, language: nil, connected: false, status: "chat", chat_enabled: true, occupation: nil, invitation_token: nil, invitation_created_at: nil, invitation_sent_at: nil, invitation_accepted_at: nil, invitation_limit: nil, invited_by_id: nil, invited_by_type: nil>
What options I can set for this? Thanks in advance!
I researched some ActiveRecord (v3.2.21) code, It seems that there is no option about the SQL result. The above log User Load (1.0ms) SELECT "users".* FROM "users" LIMIT 1 is finished through callingActiveSupport::Notifications.instrument():
# query_cache.rb
def cache_sql(sql, binds)
result =
if #query_cache[sql].key?(binds)
ActiveSupport::Notifications.instrument("sql.active_record",
:sql => sql, :binds => binds, :name => "CACHE", :connection_id => object_id)
#query_cache[sql][binds]
else
#query_cache[sql][binds] = yield
end
result.collect { |row| row.dup }
end
So if I want to see the result in log I can add a log statement:
logger.info "#{result}"
result.collect { |row| row.dup }

Query to find "active" ads in Rails

I'm building an ad manager that has the following Schema:
<Campaign id: nil, target_id: nil, date_view_based: nil, start_date: nil, end_date: nil, max_views: nil, cur_views: nil, link_to: nil, alt_data: nil, title_attr: nil, created_at: nil, updated_at: nil, user_id: nil, image_file_name: nil, image_content_type: nil, image_file_size: nil, image_updated_at: nil, link_clicked: nil, name: nil>
What I need is a way to create a method that will run like this...
Campaigns.current?
And output an array of something like this
Campaign.where('date_view_based =? AND end_date >= ? AND start_date <= ?', "Date", Date.today.strftime('%m/%d/%Y'), Date.today.strftime('%m/%d/%Y')).concat(Campaign.where('date_view_based = ? AND max_views >= ?, "View", :cur_views))
However, the Campaign.where('max_views >= ?, :cur_views) doesn't work, so how can I compare these two attributes? Surely there must be a better way to do this any suggestions would be appreciated.
Sorry I thought I had included the structure but I guess I didn't: Campaign.new returns:
#<Campaign id: nil, target_id: nil, date_view_based: nil, start_date: nil, end_date: nil, max_views: nil, cur_views: nil, created_at: nil, updated_at: nil, name: nil>
How about this?
class Campaign < ActiveRecord::Base
# ...
def self.current
date_view_based_enum = %W(Date View)
date = Date.today.strftime('%m/%d/%Y')
where("((start_date <= ? AND end_date >= ?) OR (max_views >= cur_views)) AND date_view_based IN (?)", date, date, date_view_based_enum)
end
end

Set ActiveRecord Scopes In A Loop

Why doesn't this work?
class Foo
...
Status.each do |status|
scope status, where(status: status)
end
...
end
Now Foo.new it returns not an instance of Foo but an ActiveRecord::Relation.
Try this in ruby 1.9
Status.each do |status|
scope status, -> { where(status: status) }
end
or in previous ruby versions
Status.each do |status|
scope status, lambda { where(status: status) }
end
-- EDIT --
I guess your problem is somewhere else, since this code is working for me:
class Agency < ActiveRecord::Base
attr_accessible :logo, :name
validate :name, presence: true, uniqueness: true
NAMES = %w(john matt david)
NAMES.each do |name|
scope name, -> { where(name: name) }
end
end
I can create new models just fine and use the scopes
irb(main):003:0> Agency.new
=> #<Agency id: nil, name: nil, logo: nil, created_at: nil, updated_at: nil>
irb(main):004:0> Agency.matt
Agency Load (0.5ms) SELECT "agencies".* FROM "agencies" WHERE "agencies"."name" = 'matt'
=> []
irb(main):005:0> Agency.john
Agency Load (0.3ms) SELECT "agencies".* FROM "agencies" WHERE "agencies"."name" = 'john'
=> []
irb(main):006:0> Agency.david
Agency Load (0.3ms) SELECT "agencies".* FROM "agencies" WHERE "agencies"."name" = 'david'
=> []

How to write an rspec test to specify that invalid dates should not be valid for a Rails model?

View
<%= form_for(#new_credit_entry) do |f| %>
<%= f.date_select :created_on%>
I see that I am allowed to specify an invalid date. This results in a params hash as shown below
"credit"=>{"created_on(1i)"=>"2013",
"created_on(2i)"=>"2",
"created_on(3i)"=>"31"
This is of course an incorrect date. So I know my model needs to have validation for this - will probably use the validates_timeliness gem. What I need to know is how to simulate this in a spec
Here's some rails console output
irb(main):056:0> x = Credit.new(created_on: "2013-02-30")
=> #<Credit id: nil, description: nil, credit_category_id: nil, amount: nil, created_on: nil, created_at: nil, updated_at: nil>
irb(main):058:0> x.created_on_before_type_cast
=> "2013-02-30"
irb(main):060:0> x.created_on
=> nil
irb(main):057:0> y = Credit.new(created_on: "2013-03-03")
=> #<Credit id: nil, description: nil, credit_category_id: nil, amount: nil, created_on: "2013-03-03", created_at: nil, updated_at: nil>
irb(main):059:0> y.created_on_before_type_cast
=> "2013-03-03"
irb(main):061:0> y.created_on
=> Sun, 03 Mar 2013
irb(main):062:0> z = Credit.new("created_on(1i)"=>"2013",
irb(main):063:1* "created_on(2i)"=>"2",
irb(main):064:1* "created_on(3i)"=>"31")
=> #<Credit id: nil, description: nil, credit_category_id: nil, amount: nil, created_on: "2013-03-03", created_at: nil, updated_at: nil>
irb(main):065:0> z.created_on
=> Sun, 03 Mar 2013
irb(main):066:0> z.created_on_before_type_cast
=> Sun, 03 Mar 2013
If you use validates_timeliness, it will reject the invalid dates you're trying above. The model will not be valid and created_on will be nil. That includes the date wrapping issue you mention.
After installing validates_timeliness, all you need in your model is:
validates_date :created_on
Here's a spec that tests a variety of scenarios:
describe Credit do
it "rejects single digits" do
credit = Credit.new created_on: "3"
credit.should_not be_valid
credit.created_on.should be_nil
end
it "rejects bad dates" do
credit = Credit.new created_on: "2013-02-31"
credit.should_not be_valid
credit.created_on.should be_nil
end
it "rejects words" do
credit = Credit.new created_on: "some nonsense"
credit.should_not be_valid
credit.created_on.should be_nil
end
it "accepts good dates" do
date = "2013-02-28"
credit = Credit.new created_on: date
credit.should be_valid
credit.created_on.should == Date.parse(date)
end
end
You could probably validate this fairly easily even without the gem, but that looks like a handy gem to use. Don't forget to run the generator to complete installation.
If you do choose to roll your own validation, you might run into some issues.
One is that underlying databases behave somewhat differently when you try to stick a bad value into a date or datetime column; some are more flexible in the values they store and the automatic conversions they do. It's best to validate on the Rails side ahead of time.
A bigger issue is that by the time validations are run, ActiveRecord has already tried to cast the value into a type that matches the database field type. If you've got a date field in the database for created_on, ActiveRecord will convert every value assigned to created_on into a Date object. That will end up being nil for many malformed dates. In your validator, you'll want to look at created_on_before_type_cast, which will be the raw string. ActiveRecord creates a dynamic *_before_type_cast method for every DB column. You can check the format of that and reject bad values.
I mention these caveats to encourage you to stick with the gem. :)

Retrieving an ActiveRecord length validation returns nil

I want to retrieve the maxmimum lenght validation of a ActiveRecord field in one of my views.
The following works fine in rails console and returns the correct value :
irb(main):046:0> micropost = Micropost.new
=> #<Micropost id: nil, content: nil, user_id: nil, created_at: nil, updated_at: nil>
irb(main):047:0> micropost._validators[:content][1].options[:maximum].to_s
=> "140"
However, when I use the same code in my controller it returns nil :
class PagesController < ApplicationController
def home
#title = "Home"
if signed_in?
#micropost = Micropost.new
#feed_items = current_user.feed.paginate(:page => params[:page])
#content_max = #micropost._validators[:content][1].options[:maximum].to_s
end
end
...
end
I also tried to include a method in my ApplicationHelper, which also returns nil ;-(
def content_max
Micropost._validators[:content][1].options[:maximum].to_s
end
What am I doing wrong?
The _validators array might not be in the same order whether you're in the console or in a web request.
Micropost._validators[:content].find {|v| v.class == ActiveModel::Validations::LengthValidator} .options[:maximum].to_s
should do what you want.
IMHO, a better solution would be to store the length in a constant (I somehow doubt the _validators array is part of the official API) :
class Micropost < ActiveRecord::Base
MAX_CONTENT_LENGTH = 140
validates :content, :length => {:maximum => MAX_CONTENT_LENGTH}
# Rest of the code...
end
and get the length with :
Micropost::MAX_CONTENT_LENGTH