Change value of custom field in Redmine hook - ruby-on-rails

The problem:
My issues currently have 3 custom fields, let's say FieldA (select list), FieldB (irrelevant), and FieldC (text).
What needs to happen is that on save, FieldC takes the value of <FieldA>-<date in Ymd>-<number from database>
As an example, let's assume that FieldA has the value "Test", and today is the 8th of Jan 2015. FieldC should then be Test-20150108-001 where 001 comes from a custom table in the database, that's unique per value of FieldA, and resets every year.
What I've done to now:
I've used the command line script to generate a plugin via
ruby script/rails generate redmine_plugin subticket
And a model via
ruby script/rails generate redmine_plugin_model subticket subticket_ids fa:string lastnum:integer year:integer
( where fa is the value of FieldA, lastnum is the last number used for that value, and year is the current year for which the lastnum is applicable ).
I've then proceeded to prepend init.rb to add a hook listener in order to implenent the presave hooks:
require_dependency 'subticket_hooks'
And created the file lib/subticket_hooks.rb with the following content:
class SubticketHooksListener < Redmine::Hook::ViewListener
def controller_issues_edit_before_save(context={})
issue = context[:issue]
end
end
However, I cannot find any documentation on how to access/write the value of a custom field here. Still working on making the model work as well, but I assume the documentation is clear enough on that for me to experiment (of course any info is welcomed!)
Do note that this is way beyond my abilities since my core expertise is in a completely different language - take it slow!

I had the same task
My solution:
Every customizable redmine object has custom_field_values field, that value is array of CustomFieldValue. CustomFieldValue contains current value, custom field description and customized object.
Needed values i reads and alters by sort out. May be it's not best variant, but i acquainted with ruby language not so long ago.
Method for reading custom fields values:
def object_custom_field_value(object, field_name)
object.custom_field_values.each do |field|
if field.custom_field.name == field_name
return field.value
end
end
end
And for changing:
def object_custom_field_set_value(object, field_name, value)
object.custom_field_values.each do |field|
if field.custom_field.name == field_name
field.value = value
end
end
end
Hope this helps!

Get: object.custom_field_value(field.id)
Update: object.custom_field_values = {field.id => val}. Don't forget to save: object.save_custom_field_values. However it doesn't work for creating value, so check via object.custom_value_for(field.id).id?
Create/Update:
Easy. Just add a line object.custom_vield_values before update code. It returns list of values, and triggers creation of empty value. Example
u = User.find(1)
cf = CustomField.where('name', 'mobile phone').first
u.custom_field_values # returns values, triggers creation
u.custom_field_values = {cf.id = "+123456789"} # set value
u.save_custom_field_values # save
Docs: rubydoc

Updating a project's custom field named 'Email':
project = Project.find(1)
cv = CustomValue.where(customized_type: "Project", customized_id: project.id).includes(:custom_field).where(custom_fields: {type: 'ProjectCustomField', name: 'Email'}).first
cv.update value: 'email#mail.com'

Related

How does one access the current id sequence value in the activerecord initializer?

I'm building an activerecord to model a conversation tree, using an array column type to represent the materialized path of the record's place in that tree, using postgres 9.1, rails 4.0, and the pg gem.
What I really want to do is access currval('conversations_id_seq') when I create a new conversation object, so that I can pass in [grandparent_id, parent_id ... current_id] as the array to the object initializer. That way I can specify that this column is not null as a database constraint, and in the event of a parentless conversation, have it still default to [current_id].
The problem I have is getting access to the model's id value before I save it the first time. I could always relax the not null constraint and add an after_create hook, but that feels kludgy. I'm hopeful that there's a way I can grab the value that's getting pushed into #id inside the initializer, before the first save to the database.
EDIT to clarify for the bounty: In an ideal world, there would be a special token I could pass in to the object's create method: Conversation.create(reply_chain: [:lastval]), where the gem took that to mean lastval() in the generated SQL.
something like:
def before_create
self.id=Conversation.connection.execute("SELECT nextval('conversations_id_seq')")
self.path = [... , self.id];
true
end
or use a before insert/update trigger to maintain the path.
You could alias the attribute if you don't need the column in the database.
alias_attribute :current_id, :id
Or you could query for the id when you need it.
def self.last_val
ActiveRecord::Base.connection.execute("SELECT lastval('conversations_id_seq')")
end
def self.next_val
ActiveRecord::Base.connection.execute("SELECT nextval('conversations_id_seq')")
end
Conversation.create(reply_chain: Conversation.next_val)
Using after_save isn't the ugliest of code either.

Setting virtual model attribute from controller create action in Rails 4 - attribute always nil

Some background: I have a Rails 4 model with a decimal column d. When being saved or viewed in the app, the value may need to be converted to or from an integer value, respectively, based on the current user's preference.
It seems most appropriate that the calculations should go in the model, and should have no knowledge of users.
After doing some searching, it seems to me the best way to achieve this is via 2 virtual attributes on the model: one for the current user's preferred format and one for the value, with any calculation applied.
So I have come up with something like this:
attr_accessor :format, :value_converted
def value_converted
if format.nil?
format = 'A'
end
if format == 'A'
Converter.new(d).to_i if d
else
d if d
end
end
def value_converted=(value)
if format.nil?
format = 'A'
end
if format == 'A'
self.d = Converter.new(value.to_i).to_f
else
self.d = value
end
Forms refer to the value_converted rather than the d, and this works fine for editing, updating and viewing. The problem is the #create action.
I believe the issue is happening because the setter is accessing format, and I can't seem to figure out a way to set format from #create before the setter is called, in other words when new creates the object.
I thought perhaps I could just pass the format along with the other parameters (as in this answer, so I tried merging it in, both within and after the standard strong parameters fare, but with no luck:
Both
#entry = current_user.entries.new(entry_params.merge(format: current_user.format))
and
params.require(:entry).permit(:value_converted, :other_param, ...).merge(format: current_user.format)
do not raise any errors, but are apparently ignored. However simply passing format to new in the console works fine.
So my question: Does anyone have a solution to this problem? Or perhaps a better/more appropriate way of going about it? It seems like it should be something simple.
Thanks for any help
For the moment, I got around this by simply setting the values of format and value_converted again in the controller before saving:
def create
#entry = current_user.entries.new(entry_params)
#entry.format = current_user.format
#entry.value_converted = entry_params[:value_converted]
if #entry.save
...
end
Though this is probably not the most elegant solution (I have no idea whether my implementation is thread-safe) it does seem to work.

How to define a Form Override for a Chained Form Field in Active Scaffold

Below example is taken from this documentation page:
https://github.com/activescaffold/active_scaffold/wiki/Chaining-Form-Fields
[Example start]
You can set an array of columns to update multiple columns when a column changes, and chain column updates:
class UsersController < ApplicationController
active_scaffold do |config|
config.columns[:author].form_ui = :select
config.columns[:author].update_columns = [:book, :editorial]
config.columns[:book].form_ui = :select
config.columns[:book].update_columns = :format
end
end
In this example, fields for book, editorial and format are updated when author changes, and when book changes only format is updated. A form override which use the new author or book must be defined for editorial and format columns, in other case those fields won’t change when they will be rendered again.
[Example end]
In the example it states "a form override which use the new author or book must be defined".
Question is how to define those form overrides ??
I have read the documentation on https://github.com/activescaffold/active_scaffold/wiki/Form-Overrides, and tried different form overrides, but with no luck so far, i.e. the columns are not being rendered again.
If you can help me with the code for those form overrides needed in the given example, then I should be able to port that to my code.
Here is the solution to my problem:
I followed the example on "https://github.com/activescaffold/active_scaffold/wiki/Chaining-Form-Fields", but when it did not work for my chained columns (when updating the first column all chained columns updates correctly, but when updating the second column then its chained columns renders to blank lists), then I focused (blindly?) on the details explained just below the example as I thought this was the first step to solve my problem: "A form override which use the new author or book must be defined for editorial and format columns, in other case those fields won’t change when they will be rendered again".
This was however not the case, no form override in the helper was needed to get this to work, in the helper the "options_for_association_conditions" is enough. As the example is for v2.4, maybe the form override is not needed anymore in v3.0+.
The solution is in the next paragraph on the example wiki: "Usually only the value of the changed column is sent, if you need another values to render the updated columns, enable send_form_on_update_column and all the form will be sent". My problem was, that the columns which was chained from the second column needed the value from the first column also, so setting up the second column with "send_form_on_update_column" (i.e. sending the whole form, not just its own value) solved my problem.
In the example this would be:
config.columns[:book].send_form_on_update_column = true

Rails: oracle set_sequence_name being ignored

I have a simple model which I have to set the database name manually on.
Also since it is using an oracle database, I'm setting the sequence name so I can have auto incrementing id's.
When I run the rails console and try to create my model, it comes back and says that the sequence cannot be found. The weird part is the sequence it cannot find is not the sequence that I set in set_sequence_name.
Model
class Survey < ActiveRecord::Base
set_sequence_name "SURVEY.SQ_SURVEY_ID"
set_table_name "SURVEY.SURVEYS"
end
Console error
ActiveRecord::StatementInvalid: ActiveRecord::JDBCError: ORA-02289:
sequence does not exist: select SURVEY.SURVEYS_seq.nextval id from dual
It looks like its ignoring my set sequence name line.
Am I just missing something?
FWIW:
Using 11g I got away with:
self.id = ActiveRecord::Base.connection.execute("select SURVEY.SQ_SURVEY_ID.nextval id from dual").fetch
It appears that in my case, the sequence returns a cursor on which I need to do a fetch on.
11g/Rails 3.1
Clarifying, this works for oracle 10g
So as far as I can tell, this is a bug in the jdbc adapter (see here http://kenai.com/jira/browse/ACTIVERECORD_JDBC-133). For a work around I'm setting the id manually with a before create filter like this:
class Survey < ActiveRecord::Base
set_table_name "SURVEY.SURVEYS"
before_create do
#since we can't use the normal set sequence name we have to set the primary key manually
#so the execute command return an array of hashes,
#so we grab the first one and get the nextval column from it and set it on id
self.id = ActiveRecord::Base.connection.execute("select SURVEY.SQ_SURVEY_ID.nextval id from dual")[0]["id"]
end
end

How to execute a raw update sql with dynamic binding in rails

I want to execute one update raw sql like below:
update table set f1=? where f2=? and f3=?
This SQL will be executed by ActiveRecord::Base.connection.execute, but I don't know how to pass the dynamic parameter values into the method.
Could someone give me any help on it?
It doesn't look like the Rails API exposes methods to do this generically. You could try accessing the underlying connection and using it's methods, e.g. for MySQL:
st = ActiveRecord::Base.connection.raw_connection.prepare("update table set f1=? where f2=? and f3=?")
st.execute(f1, f2, f3)
st.close
I'm not sure if there are other ramifications to doing this (connections left open, etc). I would trace the Rails code for a normal update to see what it's doing aside from the actual query.
Using prepared queries can save you a small amount of time in the database, but unless you're doing this a million times in a row, you'd probably be better off just building the update with normal Ruby substitution, e.g.
ActiveRecord::Base.connection.execute("update table set f1=#{ActiveRecord::Base.sanitize(f1)}")
or using ActiveRecord like the commenters said.
ActiveRecord::Base.connection has a quote method that takes a string value (and optionally the column object). So you can say this:
ActiveRecord::Base.connection.execute(<<-EOQ)
UPDATE foo
SET bar = #{ActiveRecord::Base.connection.quote(baz)}
EOQ
Note if you're in a Rails migration or an ActiveRecord object you can shorten that to:
connection.execute(<<-EOQ)
UPDATE foo
SET bar = #{connection.quote(baz)}
EOQ
UPDATE: As #kolen points out, you should use exec_update instead. This will handle the quoting for you and also avoid leaking memory. The signature works a bit differently though:
connection.exec_update(<<-EOQ, "SQL", [[nil, baz]])
UPDATE foo
SET bar = $1
EOQ
Here the last param is a array of tuples representing bind parameters. In each tuple, the first entry is the column type and the second is the value. You can give nil for the column type and Rails will usually do the right thing though.
There are also exec_query, exec_insert, and exec_delete, depending on what you need.
None of the other answers showed me how to use named parameters, so I ended up combining exec_update with sanitize_sql:
User.connection.exec_update(
User.sanitize_sql(
[
"update users set name = :name where id = :id and name <> :name",
{
id: 123,
name: 'My Name'
}
]
)
)
This works for me on Rails 5, and it executes this SQL:
update users set name = 'My Name' where id = 123 and name <> 'My Name'
You need to use an existing Rails model instead of User if you don't have that.
I wanted to use named parameters to avoid issues with the ordering when I use ? or $1/$2,etc. Positional ordering is kind of frustrating when I have more than a handful of parameters, but named parameters allow me to refactor the SQL command without having to update the parameters.
You should just use something like:
YourModel.update_all(
ActiveRecord::Base.send(:sanitize_sql_for_assignment, {:value => "'wow'"})
)
That would do the trick. Using the ActiveRecord::Base#send method to invoke the sanitize_sql_for_assignment makes the Ruby (at least the 1.8.7 version) skip the fact that the sanitize_sql_for_assignment is actually a protected method.
Sometime would be better use name of parent class instead name of table:
# Refers to the current class
self.class.unscoped.where(self.class.primary_key => id).update_all(created _at: timestamp)
For example "Person" base class, subclasses (and database tables) "Client" and "Seller"
Instead using:
Client.where(self.class.primary_key => id).update_all(created _at: timestamp)
Seller.where(self.class.primary_key => id).update_all(created _at: timestamp)
You can use object of base class by this way:
person.class.unscoped.where(self.class.primary_key => id).update_all(created _at: timestamp)
Here's a trick I recently worked out for executing raw sql with binds:
binds = SomeRecord.bind(a_string_field: value1, a_date_field: value2) +
SomeOtherRecord.bind(a_numeric_field: value3)
SomeRecord.connection.exec_query <<~SQL, nil, binds
SELECT *
FROM some_records
JOIN some_other_records ON some_other_records.record_id = some_records.id
WHERE some_records.a_string_field = $1
AND some_records.a_date_field < $2
AND some_other_records.a_numeric_field > $3
SQL
where ApplicationRecord defines this:
# Convenient way of building custom sql binds
def self.bind(column_values)
column_values.map do |column_name, value|
[column_for_attribute(column_name), value]
end
end
and that is similar to how AR binds its own queries.
I needed to use raw sql because I failed at getting composite_primary_keys to function with activerecord 2.3.8. So in order to access the sqlserver 2000 table with a composite primary key, raw sql was required.
sql = "update [db].[dbo].[#{Contacts.table_name}] " +
"set [COLUMN] = 0 " +
"where [CLIENT_ID] = '#{contact.CLIENT_ID}' and CONTACT_ID = '#{contact.CONTACT_ID}'"
st = ActiveRecord::Base.connection.raw_connection.prepare(sql)
st.execute
If a better solution is available, please share.
In Rails 3.1, you should use the query interface:
new(attributes)
create(attributes)
create!(attributes)
find(id_or_array)
destroy(id_or_array)
destroy_all
delete(id_or_array)
delete_all
update(ids, updates)
update_all(updates)
exists?
update and update_all are the operation you need.
See details here: http://m.onkey.org/active-record-query-interface

Resources